diff --git a/tutorials/00_intro.md b/tutorials/00_intro.md index daccaf5..b17c8aa 100644 --- a/tutorials/00_intro.md +++ b/tutorials/00_intro.md @@ -10,15 +10,15 @@ It might happen that you encounter a more advanced concept or piece of code that ## Outline -1. FP and Haskell environment -2. Functions and basics of data types -3. Structure of code and branching in FP -4. Important data types, handling errors -5. Advanced functions -6. Type classes, general and basics -7. Advanced type classes 1 -8. Advanced type classes 2 -9. Testing and documentation -10. Web frameworks -11. Functional reactive programming -12. Debugging, benchmarking and dependent types +1. [FP and Haskell environment](01_fp-env.md) +2. [Functions and basics of data types](02_functions-types.md) +3. [Structure of code and branching in FP](03_branching.md) +4. [Important data types, handling errors](04_types-errors.md) +5. [Advanced functions, typeclasses intro](05_functions-typeclasses.md) +6. [IO, testing, and documentation](06_io-test-doc.md) +7. [Advanced type classes 1](07_common-typeclasses-1.md) +8. [Advanced type classes 2](08_common-typeclasses-2.md) +9. [Web application in Haskell](09_webapp.md) +10. [Frontend and FRP](10_frontend-frp.md) +11. [Performance and Debugging](11_performance-debug.md) +12. [GHC Extensions and Dependent Types](12_exts-deptypes.md) diff --git a/tutorials/01_fp-env.md b/tutorials/01_fp-env.md index bd6f82b..d4a4834 100644 --- a/tutorials/01_fp-env.md +++ b/tutorials/01_fp-env.md @@ -35,6 +35,12 @@ Some might not be absolutely clear to you at the moment, some are familiar from * **Referential transparency** = expression is said to be referentially transparent if it can be replaced with its corresponding value without changing the program's behavior * **Recursion** = recursion occurs when a thing is defined in terms of itself or of its type (applies for functions, for example, factorial, and for types, like tree structure) +Principles [[jdegoes](https://twitter.com/jdegoes/status/974045822424776704?s=09)]: + +1. Orthogonal Composability: composable blocks should address a single concern. +2. Maximum Polymorphism: data types & functions should require minimum structure necessary. +3. Maximum Deferment: defer types, decisions, effects, evaluation to the last moment. + ## Haskell - the programming language [Haskell] is a pure functional programming language with strong static typing and non-strict evaluation. It is also standardized (actual standard is [Haskell 2010] and 2020 is under development). Although it is language with academic and strong math background, it is being used in [research][haskell_research], [education][haskell_education] as well as in [industry][haskell_industry] for various projects. It was created as one common language based on many previous functional languages during the 1990s. Main language implementation is [Glasgow Haskell Compiler (GHC)][GHC], which we will use extensively in this course. @@ -56,7 +62,7 @@ Some might not be absolutely clear to you at the moment, some are familiar from There are several editors you may use for writing Haskell programs, most probably there is some extension for your favorite editor. We recommend one of those: * [Vim with plugins](https://wiki.haskell.org/Vim) -* [IntelliJ IDEA with HaskForce](http://haskforce.com) +* [IntelliJ IDEA with HaskForce](http://haskforce.com) (or visit their [GitHub repo](https://github.com/carymrobbins/intellij-haskforce)) * [Atom with plugins](https://atom-haskell.github.io/overview/) Most probably you will need following stuff: diff --git a/tutorials/02_functions-types.md b/tutorials/02_functions-types.md index 57b1a6c..af07405 100644 --- a/tutorials/02_functions-types.md +++ b/tutorials/02_functions-types.md @@ -495,7 +495,7 @@ factorial n = fac' n 1 2. `fac' 3 1` 3. `fac' 2 3` 4. `fac' 1 6` -5. `fac' 1 6` +5. `fac' 0 6` 6. `6` Although Haskell's [lazy evaluation] strategy and GHC optimizations make it unnecessary to write tail-recursive functions, you should be familiar with the concept as functional programmer. With Haskell, you should more focus on the readability of your code and productivity! diff --git a/tutorials/04_types-errors.md b/tutorials/04_types-errors.md index ed7dee4..97aa0eb 100644 --- a/tutorials/04_types-errors.md +++ b/tutorials/04_types-errors.md @@ -159,7 +159,8 @@ Prelude T B C> C.index cstr 2 In other cases you need to use encoding to encode/decode bytes to/from text: ``` -E.encodeUtf8 (T.pack "život, жизнь, lífið, ਜੀਵਨ, ,حياة") +Prelude T B> import qualified Data.Text.Encoding as E +Prelude T B C E> E.encodeUtf8 (T.pack "život, жизнь, lífið, ਜੀਵਨ, ,حياة") "\197\190ivot, \208\182\208\184\208\183\208\189\209\140, l\195\173fi\195\176, \224\168\156\224\169\128\224\168\181\224\168\168, ,\216\173\217\138\216\167\216\169" Prelude T B C E> x = E.encodeUtf8 (T.pack "život, жизнь, lífið, ਜੀਵਨ, ,حياة") Prelude T B C E> x @@ -174,7 +175,7 @@ Prelude T B C E> index x 2 As needing to pack all string literals when using non-base string representations is cumbersome, there is a handy [GHC] language extension [OverloadedStrings](https://ocharles.org.uk/blog/posts/2014-12-17-overloaded-strings.html). -Generally, [GHC] language extensions can be enabled in the source file using pragma `LANGUAGE` as the first line in the file:: +Generally, [GHC] language extensions can be enabled in the source file using pragma `LANGUAGE` as the first line in the file: ```haskell {-# LANGUAGE OverloadedStrings #-} @@ -184,13 +185,13 @@ module XY ... In GHCi, the extension can be enabled using the `:set` directive: -```haskell +``` Prelude> :set -XOverloadedStrings ``` After that, a string literal type can be inferred by its usage in the source code: -```haskell +``` Prelude> import qualified Data.Text as T Prelude T> :type "abc" "abc" :: [Char] @@ -498,5 +499,5 @@ The homework to practice working with new types, list comprehensions, containers [containers]: https://hackage.haskell.org/package/containers [GHC]: https://www.haskell.org/ghc/ [Hackage]: https://hackage.haskell.org -[Hayoo!]: https://hayoo.fh-wedel.de +[Hayoo]: https://hayoo.fh-wedel.de [Hoogle]: https://www.haskell.org/hoogle/ diff --git a/tutorials/05_functions-typeclasses.md b/tutorials/05_functions-typeclasses.md index e592774..6f2e3f6 100644 --- a/tutorials/05_functions-typeclasses.md +++ b/tutorials/05_functions-typeclasses.md @@ -8,7 +8,7 @@ Creating new own functions or using the predefined ones from libraries is common When we talk about "currying", in Haskell it has (almost) nothing to do with dishes or spices. A famous mathematician and logician [Haskell Curry](https://en.wikipedia.org/wiki/Haskell_Curry) (the language is named after him) developed with others technique called currying: *translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument*. Technically, the original author of this is [Moses Schönfinkel](https://en.wikipedia.org/wiki/Moses_Sch%C3%B6nfinkel), so sometimes you may even come across a very nice name ["Schönfinkelization"](http://www.natansh.in/2012/07/27/schonfinkelization/). -Curyying can be achieved in all functional programming languages, but Haskell is special in that *all functions are curried by default*, similarly to pure lambda calculus. Let's se how we parenthesize function types: +Currying can be achieved in all functional programming languages, but Haskell is special in that *all functions are curried by default*, similarly to pure lambda calculus. Let's se how we parenthesize function types: ```haskell myFunc1 :: a -> b -> c @@ -45,7 +45,7 @@ type PSize = Int type NoVertices = Int data Polygon = -- some representation -mkPolygon :: PSize -> NoVertices -> Polygon +mkPolygon :: NoVertices -> PSize -> Polygon mkPolygon = -- some code to make a polygon mkHexagon :: PSize -> Polygon @@ -56,28 +56,28 @@ mkRectangle = mkPolygon 4 --etc. ``` -Here we create *specialized* versions of polygon constructor functions by providing the `PSize` parametre. As functions can be parametres, as well, we can reify the behaviour, as well: +Here we create *specialized* versions of polygon constructor functions by providing the `PSize` parameter. As functions can be parameters, as well, we can reify the behaviour, as well: ```haskell -generalSort :: (Something -> Something -> Ordering) -> [Something] -> [Int] +generalSort :: Ord a => (a -> a -> Ordering) -> [a] -> [a] generalSort orderingFn numbers = -- use the orderingFn to sort the numbers -fastOrderingFn :: Something -> Something -> Ordering +fastOrderingFn :: Ord a => a -> a -> Ordering fastOrderingFn = -- a fast, but not too reliable ordering algorithm -slowOrderingFn :: Something -> Something -> Ordering +slowOrderingFn :: Ord a => a -> a -> Ordering slowOrderingFn = -- a slow, but precise ordering algorithm -fastSort :: [Something] -> [Something] +fastSort :: Ord a => [a] -> [a] fastSort = generalSort fastOrderingFn -goodSort :: [Something] -> [Something] +goodSort :: Ord a => [a] -> [a] goodSort = generalSort slowOrderingFn ``` This technique is very elegant, DRY and it is a basis of a good purely functional style. Its object-oriented relatives are the [Template Method design pattern](https://en.wikipedia.org/wiki/Template_method_pattern) brother married with the [Factory Method design pattern](https://en.wikipedia.org/wiki/Factory_method_pattern) – quite some fat, bloated relatives, aren't they? -As you can see, the "parametrising" parametres must come first, so we can make a curried version of the constructor function. At the same time, the order of parametres can be switched using the `flip` function that takes its (first) two arguments in the reverse order of `f`: +As you can see, the "parametrising" parameters must come first, so we can make a curried version of the constructor function. At the same time, the order of parameters can be switched using the `flip` function that takes its (first) two arguments in the reverse order of `f`: ```haskell flip :: (a -> b -> c) -> b -> a -> c @@ -108,7 +108,7 @@ fastSort :: [Something] -> [Something] fastSort numbers = generalSort numbers fastOrderingFn ``` -As we said, all functions in Haskell are curried. In case you want to make them not curried, you can use tuples to "glue" parametres together: +As we said, all functions in Haskell are curried. In case you want to make them not curried, you can use tuples to "glue" parameters together: ```haskell notCurried :: (a, b) -> (c, d) -> e @@ -231,7 +231,7 @@ Prelude> 7 `div` 2 3 Prelude> foo x y z = x * (y + z) Prelude> (5 `foo` 3) 12 -65 +75 ``` You can define own operator as you would do it with function: @@ -239,9 +239,9 @@ You can define own operator as you would do it with function: ``` Prelude> (><) xs ys = reverse xs ++ reverse ys Prelude> (><) "abc" "xyz" -"zyxcba" +"cbazyx" Prelude> "abc" >< "xyz" -"zyxcba" +"cbazyx" Prelude> :info (><) (><) :: [a] -> [a] -> [a] ``` @@ -354,8 +354,9 @@ GHC has an extension of [Generalized Algebraic Data Types (GADTs)](https://en.wi An anonymous function is a function without a name. It is a Lambda abstraction and might look like this: `\x -> x + 1`. Sometimes, it is more convenient to use a lambda expression rather than giving a function a name. You should use anonymous functions only for very simple functions because it decreases readability of the code. ```haskell -myFunc1 = (\x y z -> x * y + z) -myFunc2 x y z = x * y + z +myFunc1 x y z = x * y + z -- <= just syntactic sugar! +myFunc2 = (\x y z -> x * y + z) -- <= still syntactic sugar! +myFunc3 = (\x -> \y -> \z -> x * y + z) -- <= desugarized function mapFunc1 = map myFunc1 mapAFunc1 = map (\x y z -> x * y + z) ``` @@ -428,8 +429,8 @@ Let's make a generalized higher-order function that also takes an initial value ```haskell process :: (a -> a -> a) -> a -> [a] -> a -process _ initValue [] = initValue -process f _ (x:xs) = f x (process xs) +process _ initValue [] = initValue +process f initValue (x:xs) = f x (process f initValue xs) mySum = process (+) 0 myProduct = process (*) 1 @@ -438,7 +439,14 @@ myProduct = process (*) 1 But here we are getting into a problem. Both `(+)` and `(*)` use operands and result of the same type - if we want to convert a number to string and join it in one go with `process`, it is not possible! ``` -Prelude> process (\x str -> show x ++ str) "" [1,2,3,4] +*Main> process (\x str -> show x ++ str) "" [1,2,3,4] + +:18:39: error: + • No instance for (Num [Char]) arising from the literal ‘1’ + • In the expression: 1 + In the third argument of ‘process’, namely ‘[1, 2, 3, 4]’ + In the expression: + process (\ x str -> show x ++ str) "" [1, 2, 3, 4] ``` The type of the initial value must be the same as the type which is returned by given function. Now we get this: @@ -446,11 +454,13 @@ The type of the initial value must be the same as the type which is returned by ```haskell process :: (a -> b -> b) -> b -> [a] -> b -process _ initValue [] = initValue -process f _ (x:xs) = f x (process xs) +process _ initValue [] = initValue +process f initValue (x:xs) = f x (process f initValue xs) mySum = process (+) 0 myProduct = process (*) 1 + +myToStrJoin :: (Show a) => [a] -> String myToStrJoin = process (\x str -> show x ++ str) "" ``` @@ -459,17 +469,20 @@ Now problem is that both `(+)` and `(*)` are commutative, but `(\x str -> show x ```haskell processr :: (a -> b -> b) -> b -> [a] -> b -- "accumulates" in the RIGHT operand -processr _ initValue [] = initValue -processr f _ (x:xs) = f x (processl xs) +processr _ initValue [] = initValue +processr f initValue (x:xs) = f x (processr f initValue xs) processl :: (b -> a -> b) -> b -> [a] -> b -- "accumulates" in the LEFT operand -processl _ initValue [] = initValue -processl f _ (x:xs) = f (processr xs) x +processl _ initValue [] = initValue +processl f initValue (x:xs) = f (processl f initValue xs) x mySum = processl (+) 0 myProduct = processl (*) 1 + +myToStrJoinR :: (Show a) => [a] -> String myToStrJoinR = processr (\x str -> show x ++ str) "" -myToStrJoinL = processl (\x str -> show x ++ str) "" +myToStrJoinL :: (Show a) => [a] -> String +myToStrJoinL = processl (\str x -> show x ++ str) "" ``` This is something so generally useful, that it is prepared for you and not just for lists but for every instance of typeclass `Foldable` - two basic folds `foldl`/`foldr` and related `scanl`/`scanr`, which capture intermediate values in a list: