diff --git a/project-guide.md b/project-guide.md new file mode 100644 index 0000000..bdbe6cd --- /dev/null +++ b/project-guide.md @@ -0,0 +1,161 @@ +# Open-Source Project Guide + +This guide is part of [MI-AFP](https://github.com/MI-AFP) course of functional programming but brings and summarizes broader ideas what to consider when developing an open-source project, i.e., not necessarily in Haskell or Elm. We try to cover all important aspects and use references to existing posts and guides with more complete descriptions and explanations. If you find anything wrong or missing, feel free to contribute in open-source matter :wink: + +## Think Before Coding + +When you have ideas or some assignment what to program, you can easily create project and start coding right away. That might go well for some smaller project but sometimes even for tiny utility script you might end up shortly in "no idea" zone. Usually, it is better if you think first about what you want to accomplish, how it can be done, and then setup some productive enviroment for work itself. + +### Requirements and expectations + +At first, do a mindmap (it is really helpful tool for creativity) or a list of things that your project should do in terms of functionality and its behaviour and look. In software engineering, there are functional and non-functional requirements. Functional are trivital, just list what the app should do, e.g., communicate with GitHub API to list labels of given repository. + +On the other hand, non-functional requirements might be more complicated to summarize. They cover many areas and some can be, for instance, it will be in form of web application, it will use relational database, it will have internationalization, and so on. In general, for requirements, you should think about [FURPS](https://en.wikipedia.org/wiki/FURPS): + +* Functionality - Capability (Size & Generality of Feature Set), Reusability (Compatibility, Interoperability, Portability), Security (Safety & Exploitability) +* Usability (UX) - Human Factors, Aesthetics, Consistency, Documentation, Responsiveness +* Reliability - Availability (Failure Frequency (Robustness/Durability/Resilience), Failure Extent & Time-Length (Recoverability/Survivability)), Predictability (Stability), Accuracy (Frequency/Severity of Error) +* Performance - Speed, Efficiency, Resource Consumption (power, ram, cache, etc.), Throughput, Capacity, Scalability +* Supportability (Serviceability, Maintainability, Sustainability, Repair Speed) - Testability, Flexibility (Modifiability, Configurability, Adaptability, Extensibility, Modularity), Installability, Localizability + +Also, you need to consider your own expectations: + +* What do you want to learn +* Are you going to work in team or alone +* Do you want to publish it or is it just private work +* etc. + +### Do some research + +Hmm, you have requirements... so you can start to work. Well you can, but maybe it is good idea to now take your list of requirements and do a bit of reasearch what solutions already exist. Not only that you might find out that you would totally "reivent the wheel" and the software you want to develop is already here and you would just waste your precious time, but you might get inspired and learn from other's mistakes. + +1. Find (for example using Google) similar existing solutions if there are any +2. Take a look at each and write down what your like and dislike and how much it fulfils your requirements +3. If some fulfils its completely and you like it, you don't need this project probably +4. Adjust your requirements based on research and/or make summary list what to avoid in your app (what you didn't like in the existing solutions) + +### Language, framework, libraries + +Before you start, you need to consider what you will use. What programming language(s) are you going to code in, what framework will you use and what external libraries. That will also highly affect tools, the architecture, and overall structure of your project. These technologies are filtered mostly by: + +* non-functional requirements (such as type of application, speed, accessibility, etc.), +* what are you able to work in (or able/keen to learn it), +* what are you allowed to used (licenses or internall restrictions). + +If there are still more options remaining, do the research how is the specific technology widespread and how it suits your task. A bigger and more active community around the technology means better for your project since there will be people to answer your questions eventually. Important last word about this, sometimes it is healthy to get our of your comfort zone and try something new that you are not totally familiar with, spend time to learn and try it (if you can)... + +### Setup productive environment + +Finally you know what are you going to code and what technologies will be used. That is sufficient to easily setup the environment for you to code efficiently: + +* [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) - the central point where you will work, there are again many options based on the technologies you want to use - [IntelliJ IDEA](https://www.jetbrains.com/idea/), [VS Code](https://code.visualstudio.com), [Visual Studio](https://visualstudio.microsoft.com/cs/), [PyCharm](https://www.jetbrains.com/pycharm/), [KDevelop](https://www.kdevelop.org), [NetBeans](https://netbeans.org), [Eclipse](https://www.eclipse.org/ide/), etc. +* Plugins for technologies - IDE usually can be enhanced with plugins specialized for your programming language(s) and framework(s), usually it consists of syntax highliting, linter, auto completion, preconfigured action, or code snippets +* VCS (see below) - ideally integrated within IDE +* Build & test automatization - it is nice to be able to build, run, or test the application without leaving IDE and ideally with single action + +Don't forget, that this is highly subjective and depends really on you what you prefer. Some people are really happy with [Vim](https://www.vim.org) and its plugins. Some prefer other simpler editors with UI similar to [Notepad++](https://notepad-plus-plus.org/). But most of programmers like full-fledged IDE providing all what they need easily. + +## VCS + +Important part of a productive environment (and not just for team, even if you are the only developer) is something called Version Control System. As the name is well-descriptive, it allows you to keep track of changes of your work and in the most tools not just in single line but in branches that can be merged afterwards. It allows you also to go back when needed, check who made what changes and when, tag some specific revisions and many more. + +### Git vs. SVN + +The most famous version control systems these days are [Git](https://git-scm.com) and [SVN (subversion)](https://subversion.apache.org). Frequently, you can stick with rule that SVN is more suitable for larger and binary files (although there is Git LFS) or when good integration for specific tool is supplied (for example, Enterprise Architect). The core difference is in approach that SVN is centralized and you need to lock part that you want to change, this is where Git will require you resolve merge conflicts. Merging and branches in SVN are more complicated than in Git (this many be subjective). + +![Git vs. Subversion (git-tower.com)](https://www.git-tower.com/learn/content/01-git/01-ebook/en/02-desktop-gui/07-appendix/02-from-subversion-to-git/centralized-vs-distributed.png) + +For more see: https://www.perforce.com/blog/vcs/git-vs-svn-what-difference + +### GitHub, GitLab, and others + +Of course, you can use Git only locally or deploy your own basic Git server to have backups and you will have your work versioned and backuped. But to gain the most of it, using public web-based hosting services for Git is essential. There are more but for the widely used, we can enumerate: [GitHub](https://github.com), [GitLab](https://about.gitlab.com), and [BitBucket](https://bitbucket.org). The main advantage is in public sharing of open-source project for free and easily opening them to contributions. + +Aside from the basic Git functionality, related features to support the software development process are supplied. One of the key features are issues for reporting bugs, sharing ideas, and discussing related stuff. Others are Pull Requests (or Merge Requests) with reviews that allow to control contributions from externals into the project as well as merge of internal branches of multiple developers. Moreover, many functions for project management (e.g., kanban boards, labels, assignments, and time tracking) and documentation (wiki pages, web-based editors, documentation sites integrations, etc.) are available. + +Another important advantage is that there are more services that can be integrated with these hostings such as CI, code analytics, deployment environments, project management systems, etc. As a disadvantage one can see providing own work publically or to some company. For that, there are private repositories (sometimes paid) or you can create own deployment of GitLab on your server or for organisation and then share the work publically but under full control. + +### Commits and branches + +Git allows you to develop in branches and make units of change called commits. A very good practice is to setup guidelines for you, your team, and/or external contributors how to split work into branches and how to compose commit messages. There is so-called [Gitflow Workflow](https://cs.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) that describes naming and branching of code in universal and nice way. Relating branches and Pull Requests to certain issues seems to be useful for tracking. + +Important also is to have **atomic commits** so one commit is only one change of your code. That helps you when going through the changes in time and cherrypicking some of them or alternating them. A good description of commit is vital as well. There is always a short message but then you can include a more elaborate description if needed. But it should be always clear from the message what the commit changed in the project and even when you don't know the previous or following messages. Messages such as "Bugfix app", "Web improvement", "Another enhancement", "Trying again", are really bad! + +![Git Flow (Atlassian)](https://wac-cdn.atlassian.com/dam/jcr:61ccc620-5249-4338-be66-94d563f2843c/05%20(2).svg?cdnVersion=lf) + +## Documentation + +Imagine your have a great piece of software that can help a lot of people, it is somewhere but noone knows what it is and how to use it. To help with that, you need to provide a documentation. Of course, you should always use "standard" way of installation for given programming language and platform as well as follow conventions how to use the app, but even dummy user should be able to get the information somewhere. + +Your documentation should always cover: + +* What is your project about +* How to install and use the application (as easy as possible) +* Configuration details and more options ("advanced stuff") +* API (if library or service) +* How to contribute (if opensource) + +For documentation, you can write your own, you can try to fit in single README file (for smaller projects) or use some service such as [GitHub Pages](https://pages.github.com), [ReadTheDocs](https://readthedocs.org), or [GitBook](https://www.gitbook.com) (there are many more). There are more views on your project - user, administrator (for example in case of web application), and developer. You should provide documentation for all of them with some expectations about their knowledge and experience. For developers, standard documentation directly in the code can be very useful. + +## Quality + +Quality of your code (as well as other parts of your project including documentation, communication, and quality assurance itself) is important for others and you too. Maintaining code that is well-writen using standard consistent conventions, is properly tested (i.e. works as it should), and is easy to read and handle, is a way easier. You should invest the time to assure quality of your project and it will definitely pay back. + +### Testing + +When talking about quality of code, the first that comes to mind of most people is testing. Of course, you should properly test your software with unit and integration tests. For the selected programming language(s) and framework(s) there should be some existing testing frameworks and guidelines how to write tests, how to make mocks, spies, and other tricks in testing. Often, people measure "coverage" which percentage of statements covered by your tests. It can be useful to check whether changes in your code increase or decrease the coverage. + +But be careful, 100 % coverage does not mean that your code is working correctly. You can get such coverage easily with stupid tests that will be passed even by totally bugged implementation. When writing tests you should follow: + +* All main parts/units are tested (separately, unit tests) +* Communication between units is tested (integration tests) +* There are also "negative" tests, not only positive - i.e. don't test only that things work when they should work but also that they don't work if the shouldn't +* If external services (for example some REST API) is used, never test with live version - use mocks or records of the communication + +Interesting testing approach that is lately often used since more and more web applications are being developed is called [end-to-end tests](https://www.tutorialspoint.com/software_testing_dictionary/end_to_end_testing.htm). It tests whole application as user would use it. In case of CLI, it tries to use the app using the CLI and checks the outputs. In case of web application, it uses real (or "headless") browser and clicks or fills some forms in it and again checks how the page behaves. It takes some time to setup such tests and start writing the cases, but again - it pays off in bigger projects since usually the tests are well-reusable. + +### Code Quality + +By code quality we mean things covered by static code analysis. Tests use your app running (i.e. dynamic) but static analysis just checks the programming code without any execution. There are many [tools](https://en.wikipedia.org/wiki/List_of_tools_for_static_code_analysis) for that and they mainly cover: + +* Conformity with conventions and rules for writing code (naming variables, indentation, structuration of files, ...) +* Repetitions in code (i.e. violations against DRY principle) +* Using vulnarable code and libraries or deprecated constructs +* Not handling exceptions correctly +* Incorrect working with types +* etc. + +Moreover, some tools can be integrated with GitHub or GitLab and check all your pushed commits easily. + +### Reviews + +Programming alone has a few serious risks especially when you get lazy: + +* skipping the documentation or making it useless +* writing code in a way that only you (at least you should!) understand it +* incorporate nasty bugs +* making dummy tests or none at all + +To avoid that, you need some people to do reviews for you and do the review for others (for example, contributors to your project or team members). Moreover, pair programming is also a good practice of [extreme programming](https://en.wikipedia.org/wiki/Extreme_programming). Try to be in positive mood both when writing review and being reviewed, in both cases you can learn a lot from others who has different experience in software development. + +## Continuous Integration, Inspection, and Delivery + +As already being said few times, there are services that can be easily integrated with GitHub or GitLab in order to do something with your project automatically when you push commits, create pull request, release new version, or do some other action. The "something" can be actually anything and you can write such "[CI tool](https://en.wikipedia.org/wiki/Continuous_integration)" even by yourself. + +* Continuous Integration - This term is used usually when your software is built (all parts are integrated), and tested with automatic tests or at least attempt to execute it is done. The result is that at that moment your code is (at least somehow) working. It is nice to see in your commits history which were working and which were not. For this, you can use [Travis CI](travis-ci.org), [Semaphore CI](https://semaphoreci.com), AppVeyor, and many others. +* Continuous Inspection - Services that does static inspection of your code, they can verify dependencies of your project, evaluate coverage, and others. One of the examples is [SonarQube](https://www.sonarqube.org), but again there are many similar services. +* Continuous Delivery - If your code passed the build and test phases, it can be deployed somewhere. The deployment can have many realizations, from actual deployment on some web hosting or app server (realized, for example, by service like [Heroku](https://www.heroku.com) or [AWS](https://aws.amazon.com)), though publishing new documentation on [ReadTheDocs](https://readthedocs.org), to uploading installation packages to some registry. All depends what you need, want, and how you will set it up. + +![CI and CD (dzone.com)](https://www.edureka.co/blog/content/ver.1531719070/uploads/2018/07/Asset-33-1.png) + +## License + +When you want to publish something, in our case usually programming code, you are required to state under what terms is your work published, can be used, edited, or distributed. There are many licenses for open-source and you should not write your own, try to find existing and suitable first. Very good site that can help you is [choosealicense.com](https://choosealicense.com). More complete one is located at [opensource.org/licenses](https://opensource.org/licenses). + +Also, if you create some graphics, you should consider Creative Commons license that is usually well-suitable for things like diagrams, images, textures, or posters. When your project is build from separated modules, those modules can be even licensed under different terms. For example, your library can be published under MIT and GUI app wrapping the library under GPL. Sometimes such licensing might be good, but having small app divided and licensed with many different licenses might cause headache to anyone who would like to use it. + +On the other hand, as you usually use libraries and other projects made by different people than you are (or even yours!), you need to take into account the licenses of such included and used software, graphics, and anything. If someone states that you cannot use their work in conditions you are using, then you need to find another solution. Problems are also with so-called "Share-Alike", i.e., you need to license your code under the same license (for instance, GPL works like that). That limits your options when picking a correct license... + +## Contributions + +If you develop open-source project, you need to open for contributions of other people. Sometimes this requires a time to review new code, teach people, and have a lot of patience with them. You can take advantage of it, and if you want, ask active contributors to help you with the project maintaining. That is how world of open-source works and as everything it has advantages and disadvantages. It is always up to you, what do you want to do with your own project... Good luck! diff --git a/tutorials/03_branching.md b/tutorials/03_branching.md index c4ba660..ed2b3f3 100644 --- a/tutorials/03_branching.md +++ b/tutorials/03_branching.md @@ -1,4 +1,4 @@ -# Structuration and branching +# Structuration, branching, and evaluation ## If and case @@ -417,6 +417,80 @@ show $ getSiblingsOf $ getParentOf $ head people -- A bit nicer show . getSiblingsOf . getParentOf . head $ people -- Haskell way (will be covered later on) ``` +### List comprehensions + +Creation of lists using a constructor (or its syntactic sugar) is pretty straightforward, but there are two more interesting ways for that. First is used basically for basic ranges and it is called "dot dot notation". You have seen it already. It works with types that are instances of type class `Enum` (you can check within GHCi by `:info Enum`. You can specify start, step and end of the range (inclusive), but you need to be careful with floats and doubles because of their precision - the error cumulatively grows. + +``` +Prelude> [1..10] +[1,2,3,4,5,6,7,8,9,10] +Prelude> [0,5..20] +[0,5,10,15,20] +Prelude> ['a' .. 'z'] +"abcdefghijklmnopqrstuvwxyz" +Prelude> [1.0,1.05 .. 1.2] +[1.0,1.05,1.1,1.1500000000000001,1.2000000000000002] +``` + +A more flexible way is offered by [list comprehensions](https://wiki.haskell.org/List_comprehension). This concept/construct is nowadays used in many other programming languages, as well, such as Python. In "list" you first specify an expression with variables and then after pipe `|`, there are specifications of bindings and restrictions. It is also possible to define local names with `let`. + +``` +Prelude> [n*2+1 | n <- [1..5]] +[3,5,7,9,11] +Prelude> [(i, j) | i <- [1..5], j <- [0,1]] +[(1,0),(1,1),(2,0),(2,1),(3,0),(3,1),(4,0),(4,1),(5,0),(5,1)] +Prelude> [x | x <- [0..10], x `mod` 3 == 1, x /= 7] +[1,4,10] +Prelude> take 10 [(i, j) | i <- [1..5], let k=i-5, j <- [k..6]] +[(1,-4),(1,-3),(1,-2),(1,-1),(1,0),(1,1),(1,2),(1,3),(1,4),(1,5)] +``` + +### Lazy Haskell + +As we've already seen, Haskell has lazy non-strict evaluation strategy. It means that no expression is evaluated, unless the value is needed. One of the possibilities is creating infinite lists. You may use `undefined` for testing when the expression is evaluated. + +``` +Prelude> let x = 1:x +Prelude> take 10 x +[1,1,1,1,1,1,1,1,1,1] +Prelude> take 20 x +[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] +Prelude> x +[1,1,1,1,1,1,1,1,1,1,1,1,1,1,...^C Interrupted. +Prelude> [1,2..] +[1,2,3,4,5,6,7,8,9,10,11,12,13,14,...^C Interrupted. +Prelude> let x = undefined +Prelude> let y = 1 + x +Prelude> let z = y * 2 + 15 +Prelude> :type y +y :: Num a => a +Prelude> :type x +x :: a +Prelude> z +*** Exception: Prelude.undefined +CallStack (from HasCallStack): + error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err + undefined, called at :37:5 in interactive:Ghci23 +``` + +(For stopping output press CTRL+C in GHCi) + +### Strictness with types + +In the previous lesson, we touched the topic of enforcing strictness with `!` in patterns ([bang patterns](https://ocharles.org.uk/blog/posts/2014-12-05-bang-patterns.html)) and in function application with `$!` operator. Similarly, we can use `!` with type fields like this: + +```haskell +data MyType = MyConstr Int !Int + +data MyRec = MyRecConstr { xA :: Int + , xB :: !Int + } +``` + +For both cases it means that when data constructor is evaluated, it must fully evaluate ([weak head normal form](https://wiki.haskell.org/Weak_head_normal_form)) the second parameter, but the first one will stay unevaluated in a lazy way. All depends on language implementation in the used compiler. + +In order to achieve laziness, Haskell wraps all types with some additional information = *boxed types*. If you don't need laziness and other related properties, it is more efficient to use raw *unboxed types*. We will talk about that as a bonus or at the end of course in the *Performance* section. Now you just need to know roughly what is it about, because it is used in some textual types... + ## Modules and imports A Haskell program consists of a collection of modules (similar to other programming languages). In the top level, you can declare and define data types, function, typeclasses and their instances, pattern bindings and so on. @@ -523,6 +597,130 @@ x = myFunc1 10 -- a function from TestModule y = FPTM.myFunc1 25 ``` +## Textual types + +Textual types [(strings)](https://wiki.haskell.org/Strings) are kind of pain in Haskell. This is mostly because of its long legacy and also laziness/strictness trade-offs. However, as everything, they are not really insidious, just not convenient as they could be (and actually are in newer "Haskells", such as PureScript). + +### String + +[String](https://hackage.haskell.org/package/base/docs/Data-String.html) is the string type in the `base` package. It is just a type synonym for `[Char]`, so it comes with all properties of a [list](https://hackage.haskell.org/package/base/docs/Data-List.html), and as such, it is the most common one, especially for non-performance-sensitive applications. But when it comes to performance (and sometimes even Unicode behavior), then problems arise - `String` has big overhead in time and space. + +### Text + +[Data.Text](https://hackage.haskell.org/package/text/docs/Data-Text.html) from [text](https://hackage.haskell.org/package/text) package is a time and space-efficient implementation of Unicode text. You can convert between `Text` and `String` with functions `pack` and `unpack`. The `Data.Text` package exports functions with same names as there are for `String` (`head`, `length`, `map`, `replace`, etc.), so the advised import style is `import qualified Data.Text as T`. + +``` +Prelude> import qualified Data.Text as T +Prelude T> txt = T.pack "my effective text" +Prelude T> :type txt +txt :: T.Text +Prelude T> T.index txt 1 +'y' +Prelude T> T.replace "my" "your" txt + +:13:11: error: + • Couldn't match expected type ‘T.Text’ with actual type ‘[Char]’ + • In the first argument of ‘T.replace’, namely ‘"my"’ + In the expression: T.replace "my" "your" txt + In an equation for ‘it’: it = T.replace "my" "your" txt + +:13:16: error: + • Couldn't match expected type ‘T.Text’ with actual type ‘[Char]’ + • In the second argument of ‘T.replace’, namely ‘"your"’ + In the expression: T.replace "my" "your" txt + In an equation for ‘it’: it = T.replace "my" "your" txt +Prelude T> T.replace (T.pack "my") (T.pack "your") txt +"your effective text" +Prelude T> length txt + +:11:8: error: + • Couldn't match expected type ‘[a0]’ with actual type ‘T.Text’ + • In the first argument of ‘length’, namely ‘txt’ + In the expression: length txt + In an equation for ‘it’: it = length txt +Prelude T> T.length txt +17 +``` + +There is another variant of the Text package, which is [Data.Text.Lazy](https://hackage.haskell.org/package/text/docs/Data-Text-Lazy.html), which exports same operations and thanks to laziness, it can work with huge texts and it may provide better performance under the right circumstances. [Data.Text.Encoding](https://hackage.haskell.org/package/text/docs/Data-Text-Encoding.html) (and its lazy alternative) may be also useful. + +### ByteString + +Last of the types mentioned here is [Data.ByteString](https://hackage.haskell.org/package/bytestring/docs/Data-ByteString.html) from [bytestring](https://hackage.haskell.org/package/bytestring) package. Byte vectors are encoded as strict Word8 arrays of bytes and they are used for interoperability between Haskell and C or other lower-level situations. In many ways, the usage is similar to [text](https://hackage.haskell.org/package/text) package (again `pack` and `unpack`, same basic functions, `Lazy` alternative, and so on). Next, there is an option to use vectors with `Char8` instead of `Word8`, which works as Unicode subset (0-255) strings and it is used when working with pure ASCII string representations. + +``` +Prelude T> import Data.ByteString as B +Prelude T B> bstr = B.pack [97, 98, 99] +Prelude T B> bstr +"abc" +Prelude T B> index bstr 2 +99 +Prelude T B> B.map (+1) bstr +"bcd" + +Prelude T B> import qualified Data.ByteString.Char8 as C +Prelude T B C> C.pack "abc" +"abc" +Prelude T B C> B.pack "abc" + +:28:8: error: + • Couldn't match type ‘Char’ with ‘GHC.Word.Word8’ + Expected type: [GHC.Word.Word8] + Actual type: [Char] + • In the first argument of ‘pack’, namely ‘"abc"’ + In the expression: pack "abc" + In an equation for ‘it’: it = pack "abc" +Prelude T B C> cstr = C.pack "abc" +Prelude T B C> C.index cstr 2 +'c' +``` + +In other cases you need to use encoding to encode/decode bytes to/from text: + +``` +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 +"\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> index x 0 +197 +Prelude T B C E> index x 2 +105 +``` + +### OverloadedStrings + +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: + +```haskell +{-# LANGUAGE OverloadedStrings #-} + +module XY ... +``` + +In GHCi, the extension can be enabled using the `:set` directive: + +``` +Prelude> :set -XOverloadedStrings +``` + +After that, a string literal type can be inferred by its usage in the source code: + +``` +Prelude> import qualified Data.Text as T +Prelude T> :type "abc" +"abc" :: [Char] +Prelude T> :set -XOverloadedStrings +Prelude> :type "abc" +"abc" :: Data.String.IsString p => p +Prelude T> T.length "abc" -- no need to pack the literal any more! +3 +``` + ## Task assignment The homework to practice branching and slightly working with modules is in repository [MI-AFP/hw03](https://github.com/MI-AFP/hw03). @@ -532,8 +730,13 @@ The homework to practice branching and slightly working with modules is in repos * [Learn You a Haskell for Great Good](http://learnyouahaskell.com) (chapters 4, 7) * [Haskell: Pattern matching](https://en.wikibooks.org/wiki/Haskell/Pattern_matching) * [Haskell: Control structures](https://en.wikibooks.org/wiki/Haskell/Control_structures) +* [Haskell: List comprehension](https://wiki.haskell.org/List_comprehension) +* [Haskell: Lazy evaluation](https://wiki.haskell.org/Lazy_evaluation) * [Haskell: Laziness](https://en.wikibooks.org/wiki/Haskell/Lazines) * [Haskell: Modules](https://en.wikibooks.org/wiki/Haskell/Modules) * [Haskell: Import](https://wiki.haskell.org/Import) * [Haskell: Import modules properly](https://wiki.haskell.org/Import_modules_properly). * [24 Days of GHC Extensions: Bang Patterns](https://ocharles.org.uk/blog/posts/2014-12-05-bang-patterns.html) +* [Oh my laziness](http://alpmestan.com/posts/2013-10-02-oh-my-laziness.html) +* [Haskell String Types](http://www.alexeyshmalko.com/2015/haskell-string-types/) +* [Untangling Haskells strings](https://mmhaskell.com/blog/2017/5/15/untangling-haskells-strings) diff --git a/tutorials/05_functions-typeclasses.md b/tutorials/04_containers-functions.md similarity index 64% rename from tutorials/05_functions-typeclasses.md rename to tutorials/04_containers-functions.md index 6f2e3f6..b80cabf 100644 --- a/tutorials/05_functions-typeclasses.md +++ b/tutorials/04_containers-functions.md @@ -1,4 +1,234 @@ -# Advanced functions and intro to typeclasses +# Types, containers, and advanced functions + +## Important "base" types + +We already know basic data types (from `base` package) such as [Data.Char](https://hackage.haskell.org/package/base/docs/Data-Char.html), [Bool](https://hackage.haskell.org/package/base/docs/Data-Bool.html), or [Data.Int](https://hackage.haskell.org/package/base/docs/Data-Int.html) and structures like [Data.List](https://hackage.haskell.org/package/base/docs/Data-List.html) and [Data.Tuple](https://hackage.haskell.org/package/base/docs/Data-Tuple.html) pretty well. But of course, there are more widely used types and we are going to know some more now. + +### Maybe + +In most programming languages, there is a notion of `null` or `nil` or even `None` value. Such a value is usable, but it leads often to undesired crashes of "Null pointer exception". As Haskell is type-safe, it does not allow such rogue surprises to happen, but instead deals with a possible "null" situation in a managed way. + +If we were to design such a solution, we may use ADTs like that: + +```haskell +data IntOrNull = I Int | NullInt +data StringOrNull = S String | NullString +data ValueOrNull a = Value a | Null + +myDiv :: Int -> Int -> ValueOrNull Int +myDiv x 0 = Null +myDiv x y = Value (x `div` y) + +divString :: Int -> Int -> String +divString x y = case (myDiv x y) of + Null -> "Division by zero is not allowed!" + Value res -> "Result: " ++ show res +``` + +In Haskell, we have a pretty structure called `Maybe` which does exactly that for us and there are some functions helping with common usage. It is a very important structure and you will be dealing with it very often. You can find more about in the documentation of [Data.Maybe](https://hackage.haskell.org/package/base/docs/Data-Maybe.html). + +It is defined as: + +```haskell +data Maybe a = Nothing | Just a +``` + +``` +Prelude Data.Maybe> :type Just 10 +Just 10 :: Num a => Maybe a +Prelude Data.Maybe> :type Nothing +Nothing :: Maybe a +Prelude Data.Maybe> fromJust (Just 10) +10 +Prelude Data.Maybe> fromJust Nothing +*** Exception: Maybe.fromJust: Nothing +Prelude Data.Maybe> fromMaybe "default" Nothing +"default" +Prelude Data.Maybe> fromMaybe "default" (Just "something") +"something" +Prelude Data.Maybe> catMaybes [Just 6, Just 7, Nothing, Just 8, Nothing, Just 9] +[6,7,8,9] +``` + +Is `Maybe` a good container for the following case? What if we need to propage details about the error in the communication (unknown recipient, timeout, bad metadata, etc.)? + +```haskell +-- Communicator interface +data Message = Message { msgSender :: String + , msgRecipient :: String + , msgMetadata :: [(String, String)] + , msgBody :: String + } + +sendAndReceive :: Communicator -> Message -> Maybe Message +sendAndReceive comm msg = sendSync comm msg -- some library "magic", various errors + +printReceivedMessage :: Maybe Message -> String +printReceivedMessage Nothing = "Unknown error occured during communication." +printReceivedMessage (Just msg) = msgSender msg ++ ": " ++ msgBody msg + +myCommunicator = printReceivedMessage . sendAndReceive comm +``` + +### Either + +`Maybe` is also used to signal two types of results: an error (`Nothing`) and a success (`Just value`). However, it does not tell what is the error in case of `Nothing`. There is a standard type for such use cases, and it is called `Either`: + +```haskell +data Either a b = Left a | Right b +``` + +The `Left` variant holds an error value (such as a message) and the `Right` variant holds the success result value. There are again several utility functions available (see [Data.Either](https://hackage.haskell.org/package/base/docs/Data-Either.html)): + + +``` +Prelude Data.Either> :type Left 7 +Left 7 :: Num a => Either a b +Prelude Data.Either> :type Right "Message" +Right "Message" :: Either a [Char] +Prelude Data.Either> lefts [Left 7, Right "Msg1", Left 8, Right "Msg2"] +[7,8] +Prelude Data.Either> rights [Left 7, Right "Msg1", Left 8, Right "Msg2"] +["Msg1","Msg2"] +Prelude Data.Either> partitionEithers [Left 7, Right "Msg1", Left 8, Right "Msg2"] +([7,8],["Msg1","Msg2"]) +``` + +```haskell +-- Communicator interface +data Message = Message { msgSender :: String + , msgRecipient :: String + , msgMetadata :: [(String, String)] + , msgBody :: String + } + +data CommError = Timeout + | Disconnected + | UnkownRecipient + | IncorrectMetadata + | GeneralError String + deriving Show + +sendAndReceive :: Communicator -> Message -> Either CommError Message +sendAndReceive comm msg = sendSync comm msg -- some library "magic" + +printReceivedMessage :: Either CommError Message -> String +printReceivedMessage (Left err) = "Error occured during communication: " ++ show err +printReceivedMessage (Right msg) = msgSender msg ++ ": " ++ msgBody msg + +myCommunicator = printReceivedMessage . sendAndReceive comm +``` + +### Unit + +Although we said there is no `null`, `nil` or `None`, we still have one dummy value/type called "Unit" and it is designated as an empty tuple `()`. + +``` +Prelude> :info () +data () = () -- Defined in ‘GHC.Tuple’ +Prelude> :type () +() :: () +``` + +It is semantically more similar to `void` from other languages and you can use it wherever you don't want to use an actual type. For example, using `Either` to simulate `Maybe`, you could do `Either a ()`. For more about [unit type, read wikipedia](https://en.wikipedia.org/wiki/Unit_type). + +## Other containers + +As in other programming languages or programming theory, there are various types of containers - data types/structures, whose instances are collections of other objects. As for collections with an arbitrary number of elements, we talked about lists, which are simple to use and have a nice syntactic-sugar notation in Haskell. However, there are also other versatile types of containers available in the package [containers] and others, such as [array](https://hackage.haskell.org/package/array), [vector](https://hackage.haskell.org/package/vector), and more (use [Hoogle] or [Hackage]). + +### Sequence + +The `Seq a` is a type from [Data.Sequence](https://hackage.haskell.org/package/containers/docs/Data-Sequence.html) that represents a **finite** sequence of values of type `a`. Sequences are very similar to lists, working with sequences is not so different, but some operations are more efficient - constant-time access to both the front and the rear and Logarithmic-time concatenation, splitting, and access to any element. But in other cases, it can be slower than lists because of overhead created for making operations efficient. The size of a `Seq` must not exceed `maxBound::Int`! + +``` +Prelude> import Data.Sequence +Prelude Data.Sequence> seq1 = 1 <| 2 <| 15 <| 7 <| empty +Prelude Data.Sequence> seq1 +fromList [1,2,15,7] +Prelude Data.Sequence> :type seq1 +seq1 :: Num a => Seq a +Prelude Data.Sequence> 3 <| seq1 +fromList [3,1,2,15,7] +Prelude Data.Sequence> seq1 |> 3 +fromList [1,2,15,7,3] +Prelude Data.Sequence> seq1 >< (fromList [2, 3, 4]) +fromList [1,2,15,7,2,3,4] +Prelude Data.Sequence> sort seq1 +fromList [1,2,7,15] +``` + +### Set + +The `Set e` type represents a set of elements of type `e`. Most operations require that `e` be an instance of the `Ord` class. A `Set` is strict in its elements. If you know what is *set* in math and/or programming, you can be very powerful with them. + +``` +Prelude> import Data.Set +Prelude Data.Set> set1 = insert 4 $ insert 2 $ insert 0 $ singleton 2 +Prelude Data.Set> set1 +fromList [0,2,4] +Prelude Data.Set> delete 2 set1 +fromList [0,4] +Prelude Data.Set> delete 3 set1 +fromList [0,2,4] +Prelude Data.Set> mem +member mempty +Prelude Data.Set> member 4 set1 +True +Prelude Data.Set> member (-6) set1 +False +Prelude Data.Set> Data.Set.filter (>3) set1 +fromList [4] +Prelude Data.Set> set2 = insert 5 (insert 3 (singleton 2)) +Prelude Data.Set> set2 +fromList [2,3,5] +Prelude Data.Set> set1 +fromList [0,2,4] +Prelude Data.Set> intersection set1 set2 +fromList [2] +Prelude Data.Set> union set1 set2 +fromList [0,2,3,4,5] +``` + +There is an efficient implementation of integer sets, which uses big-endian Patricia trees (works better mainly with union and intersection). Use qualified import like `import qualified Data.IntSet as IntSet` to work with it. + +### Map + +The `Map k v` type represents a finite map (sometimes called a dictionary) from keys of type `k` to values of type `v`. A Map is strict in its keys but lazy in its values (by default we use [Data.Map.Lazy](https://hackage.haskell.org/package/containers/docs/Data-Map-Lazy.html). You may use [Data.Map.Strict](https://hackage.haskell.org/package/containers/docs/Data-Map-Strict.html) instead if you will eventually need all the values stored and/or the stored values are not so complicated to compute (no big advantage of laziness). + +``` +Prelude> import Data.Map +Prelude Data.Map> map1 = insert "suchama4" "Marek Suchanek" (singleton "perglr" "Robert Pergl") +Prelude Data.Map> map1 ! "suchama4" +"Marek Suchanek" +Prelude Data.Map> map1 ! "suchamar" +"*** Exception: Map.!: given key is not an element in the map +CallStack (from HasCallStack): + error, called at ./Data/Map/Internal.hs:610:17 in containers-0.5.11.0-K2TDqgYtGUcKxAY1UqVZ3R:Data.Map.Internal +Prelude Data.Map> map1 !? "suchamar" +Nothing +Prelude Data.Map> map1 !? "suchama4" +Just "Marek Suchanek" +Prelude Data.Map> size map1 +2 +Prelude Data.Map> delete "suchama4" map1 +fromList [("perglr","Robert Pergl")] +Prelude Data.Map> delete "suchamar" map1 +fromList [("perglr","Robert Pergl"),("suchama4","Marek Suchanek")] +Prelude Data.Map> map2 = insert "suchama4" "Marek Suchanek" (singleton "stengvac" "Vaclav Stengl") +Prelude Data.Map> map2 = insert "suchama4" "Marek Sushi Suchanek" (singleton "stengvac" "Vaclav Stengl") +Prelude Data.Map> union map1 map2 +fromList [("perglr","Robert Pergl"),("stengvac","Vaclav Stengl"),("suchama4","Marek Suchanek")] +Prelude Data.Map> union map2 map1 +fromList [("perglr","Robert Pergl"),("stengvac","Vaclav Stengl"),("suchama4","Marek Sushi Suchanek")] +Prelude Data.Map> intersection map1 map2 +fromList [("suchama4","Marek Suchanek")] +``` + +Again, there is an efficient implementation of maps, where the keys are of `Int`. It uses same mechanisms as `Data.IntSet` - use `import qualified Data.IntMap as IntMap`. + +### Graph and Tree + +Finally, [containers] specify also [Data.Tree](https://hackage.haskell.org/package/containers/docs/Data-Tree.html) and [Data.Graph](https://hackage.haskell.org/package/containers/docs/Data-Graph.html), both in a very generic manner. If you ever need to work with trees or graphs, it is convenient to use those instead of reinventing the wheel yourself. ## More about functions @@ -41,12 +271,18 @@ So what is currying useful for? It enables a very powerful abstraction technique Imagine this situation of a polygon library: ```haskell -type PSize = Int -type NoVertices = Int -data Polygon = -- some representation +type Size = Double +type NoVertices = Word +newtype Polygon = Polygon [(Double, Double)] -mkPolygon :: NoVertices -> PSize -> Polygon -mkPolygon = -- some code to make a polygon +computeRegularPolygonPoints :: (Double, Double) -> NoVertices -> Size -> [(Double, Double)] +computeRegularPolygonPoints (cX, cY) nVertices r = [ (x i, y i) | i <- map fromIntegral [0..(nVertices-1)] ] + where n = fromIntegral nVertices + x i = cX + r * cos(2 * pi * i / n) + y i = cY + r * sin(2 * pi * i / n) + +mkPolygon :: NoVertices -> Size -> Polygon +mkPolygon = computeRegularPolygonPoints (0, 0) mkHexagon :: PSize -> Polygon mkHexagon = mkPolygon 6 @@ -56,23 +292,23 @@ mkRectangle = mkPolygon 4 --etc. ``` -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: +Here we create *specialized* versions of polygon constructor functions by providing the `Size` parameter. As functions can be parameters, as well, we can reify the behaviour, as well: ```haskell -generalSort :: Ord a => (a -> a -> Ordering) -> [a] -> [a] -generalSort orderingFn numbers = -- use the orderingFn to sort the numbers +genericSort :: Ord a => (a -> a -> Ordering) -> [a] -> [a] +genericSort orderingFn numbers = undefined -- use the orderingFn to sort the numbers fastOrderingFn :: Ord a => a -> a -> Ordering -fastOrderingFn = -- a fast, but not too reliable ordering algorithm +fastOrderingFn = undefined -- a fast, but not too reliable ordering algorithm slowOrderingFn :: Ord a => a -> a -> Ordering -slowOrderingFn = -- a slow, but precise ordering algorithm +slowOrderingFn = undefined -- a slow, but precise ordering algorithm fastSort :: Ord a => [a] -> [a] -fastSort = generalSort fastOrderingFn +fastSort = genericSort fastOrderingFn goodSort :: Ord a => [a] -> [a] -goodSort = generalSort slowOrderingFn +goodSort = genericSort 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? @@ -85,8 +321,10 @@ flip :: (a -> b -> c) -> b -> a -> c Then we can have: ```haskell -generalSort :: [Something] -> (Something -> Something -> Ordering) -> [Int] -generalSort numbers orderingFn = -- use the orderingFn to sort the numbers +data Something = SomethingRecord { complexFields :: () } + +genericSort :: [Something] -> (Something -> Something -> Ordering) -> [Int] +genericSort numbers orderingFn = -- use the orderingFn to sort the numbers fastOrderingFn :: Something -> Something -> Ordering fastOrderingFn = -- a fast, but not too reliable ordering algorithm @@ -379,8 +617,8 @@ myMap f (x:xs) = f x : myMap xs myFilter :: (a -> Bool) -> [a] -> [a] myFilter _ [] = [] myFilter p (x:xs) - | p x = x : filter p xs - | otherwise = filter p xs + | p x = x : myFilter p xs + | otherwise = myFilter p xs ``` That's it. Let us have some examples: @@ -523,225 +761,6 @@ Prelude> maximum [1,2,63,12,5,201,2] As an exercise, try to implement `foldl` by using `foldr` (spoiler: [solution](https://wiki.haskell.org/Foldl_as_foldr)). -## Typeclasses - -Typeclass is a class of types with a definition of common functions for all instances of that class. Please note that the term "class" here means a [mathematical class in the set theory](https://en.wikipedia.org/wiki/Class_(set_theory)), not an [object-oriented class](https://en.wikipedia.org/wiki/Class_(computer_programming))!. - -Typeclasses represent a powerful means for polymorphism in a strong static type system. They can be related to interfaces in Java or protocols in Clojure, however, they are more powerful. - -### Kinds - -In the type theory, a kind is the type of a type constructor or, less commonly, the type of a higher order type operator. A kind system is essentially a simply-typed lambda calculus 'one level up' from functions to their types, endowed with a primitive type, denoted `*` and called 'type', which is the kind of any (monomorphic) data type. Simply you can observe this with GHCi and `:kind` command on various types. For example kind `* -> *` tells that you need to specify one type argument to create a type with kind `*` so you can have values of it. - -``` -Prelude> :kind Integer -Integer :: * -Prelude> :kind Maybe -Maybe :: * -> * -Prelude> :kind Either -Either :: * -> * -> * -Prelude> :kind (Either Integer) -(Either Integer) :: * -> * -Prelude> :kind (Either Integer String) -(Either Integer String) :: * -``` - -### Polymorphism - -[Polymorphism](https://en.wikipedia.org/wiki/Polymorphism_(computer_science)) (from Greek πολύς, polys, "many, much" and μορφή, morphē, "form, shape") is the provision of a single interface to entities of different types. A polymorphic type is one whose operations can also be applied to values of some other type, or types. Polymorphism is usually connected with object-oriented programming today, however, there are even more powerful types of polymorphism in functional programming languages, such as Haskell or Clojure. - -We already used type classes and type variables - the basic enablers of polymorphism in Haskell. **Parametric polymorphism** refers to the situation when the type of a value contains one or more (unconstrained) type variables, so that the value may adopt any type that results from substituting those variables with concrete types. It is when the type variable *is not constrained* by some type class. For example, length of a list `[a]` works for any type `a`. In this tutorial, there was the `map` function with type `(a -> b) -> [a] -> [b]` - also a parametric polymorphism. This type of polymorphism doesn't know anything about the type which it will be used with. The behaviour is abstracted regardless of a concrete type. This is a somewhat limiting but extremely useful property known as versatility. - -**Ad-hoc polymorphism** refers to the situation when a value is able to adopt any one of a defined set of types because it, or a value it uses, has been given a separate definition for each of those types. This is a situation when the type variable *is constrained* by some type class. Thanks to that extra information about the given *still-generic* type, it is possible to use behaviour defined during class instantiation. Haskell even allows class instances to be defined for types which are themselves polymorphic - you can make your implementation of an arbitrary list an instance of some class. This polymorphism is not only about functions, but also about values - recall `Bounded` class with `minBound` and `maxBound` values which are different for each instance. - -There are some other types of polymorphism that are implemented in some extensions to Haskell, e.g. [rank-N types](https://wiki.haskell.org/Rank-N_types) and [impredicative types](https://wiki.haskell.org/Impredicative_types). But there are also types of polymorphism that are not supported in Haskell, for example, subtyping and inclusion polymorphism. - -### Own typeclass and instance - -There are some commonly used typeclasses and their instances available and you can create your own, as well. You need to use keyword `class` to defined functions for that typeclass and also optionally a class inheritance, i.e. a base class, which is extended. There can be even more, so the inheritance forms [lattices](https://commons.wikimedia.org/wiki/File:Base-classes.svg). - -Contrary to Java interfaces, apart from function declarations, Haskell typeclasses can also contain function implementations. - -Next, you create typeclass instances using the keyword `instance`, which means you define or (re-define) functions for that class. - -```haskell --- from https://en.wikibooks.org/wiki/Haskell/Classes_and_types#A_concerted_example --- Location, in two dimensions. -class Located a where - getLocation :: a -> (Int, Int) - -class (Located a) => Movable a where - setLocation :: (Int, Int) -> a -> a - --- An example type, with accompanying instances. -data NamedPoint = NamedPoint - { pointName :: String - , pointX :: Int - , pointY :: Int - } deriving (Show) - -instance Located NamedPoint where - getLocation p = (pointX p, pointY p) - -instance Movable NamedPoint where - setLocation (x, y) p = p { pointX = x, pointY = y } - --- Moves a value of a Movable type by the specified displacement. --- This works for any movable, including NamedPoint. -move :: (Movable a) => (Int, Int) -> a -> a -move (dx, dy) p = setLocation (x + dx, y + dy) p - where - (x, y) = getLocation p -``` - -## Basic typeclasses in detail - -### Deriving - -We have already used the keyword `deriving` many times. It is kind of magic which will automatically derive an instance of desired typeclass(es) so you don't have to write functions on your own. - -You can derive only built-in typeclasses: - -* `Eq` = (in)equality based on arguments -* `Ord` = `compare`, `min`, `max` and comparison operators -* `Show` = to `String` conversion -* `Read` = from `String` conversion -* `Enum` = enumerations only (no arguments), list `..` syntax -* `Bounded` = only for enumerations or just one arguments, `minBound` and `maxBound` - -You can use `deriving` for your own classes, but you need to put some (advanced) effort in it by using [Data.Derive](https://hackage.haskell.org/package/derive) and [Template Haskell](https://wiki.haskell.org/Template_Haskell). - -### Read and Show - -If you derive default implementation of instances for `Show` and `Read` the string representing the data is actually the same as you would write it in Haskell code: - -``` -Prelude> data Tree a = Leaf a | Node (Tree a) (Tree a) deriving (Show, Read) -Prelude> -Prelude> myTree = Node (Leaf 8) (Node (Leaf 70) (Leaf 15)) -Prelude> show myTree -"Node (Leaf 8) (Node (Leaf 70) (Leaf 15))" -Prelude> read "Node (Leaf 5) (Leaf 100)" -*** Exception: Prelude.read: no parse -Prelude> (read "Node (Leaf 5) (Leaf 100)") :: Tree Int -Node (Leaf 5) (Leaf 100) -``` - -```haskell -class Show a where - showsPrec :: Int -> a -> ShowS - show :: a -> String - showList :: [a] -> ShowS - {-# MINIMAL showsPrec | show #-} - -- Defined in ‘GHC.Show’ -``` - -When you make own `read` and `show`, you should ensure that after using `read` on a string produced by `show`, you will get the same data: - -```haskell -data Tree a = Leaf a | Node (Tree a) (Tree a) - -size :: Num a => Tree b -> a -size (Leaf _) = 1 -size (Node l r) = size l + size r - -instance (Show a) => Show (Tree a) where - show (Leaf x) = show x - show (Node l r) = show l ++ " " ++ show r -- would be possible to write read for this? -``` - - `show` is (quite often) abused to represent fancy string representations, but it is adviced to name such functions differently (such as `myShow`), or to use a pretty-printing library such as [Text.PrettyPrint](https://hackage.haskell.org/package/pretty-1.1.3.6/docs/Text-PrettyPrint.html), [pretty](https://hackage.haskell.org/package/pretty), [pretty-simple](https://hackage.haskell.org/package/pretty-simple) or [prettyprinter](https://hackage.haskell.org/package/prettyprinter). - -Typeclass `Read` is a bit more complex. If you want to make your own implementation, you need to write a parser. Parsing will be covered later on during the course. Basically, it tries to convert `String` to `a` and return it together with the remaining `String`. - -```haskell -class Read a where - readsPrec :: Int -> ReadS a - readList :: ReadS [a] - GHC.Read.readPrec :: Text.ParserCombinators.ReadPrec.ReadPrec a - GHC.Read.readListPrec :: Text.ParserCombinators.ReadPrec.ReadPrec [a] - {-# MINIMAL readsPrec | readPrec #-} - -- Defined in `GHC.Read' -``` - -### Numerics - -For numbers, there are several built-in typeclasses making numeric computing more flexible: - -* `Num` - * `Real` - * `Integral` - * `RealFrac` - * `Fractional` - * `Floating` - * `RealFloat` (subclass of `Floating` and `RealFrac`) - -If you don't need to explicitly specify the type of number, use the typeclass constraint in the declaration of function type. This is a general rule of thumb: be as much generic, as possible. - -### Comparison - -There are two basic typeclasses - `Eq` and its subclass `Ord` that specify comparisons. - -```haskell -class Eq a where - (==) :: a -> a -> Bool - (/=) :: a -> a -> Bool - {-# MINIMAL (==) | (/=) #-} - -- Defined in ‘GHC.Classes’ - -class Eq a => Ord a where - compare :: a -> a -> Ordering - (<) :: a -> a -> Bool - (<=) :: a -> a -> Bool - (>) :: a -> a -> Bool - (>=) :: a -> a -> Bool - max :: a -> a -> a - min :: a -> a -> a - {-# MINIMAL compare | (<=) #-} - -- Defined in ‘GHC.Classes’ -``` - -You can again implement your own instances of those classes: - -```haskell -data Tree a = Leaf a | Node (Tree a) (Tree a) - -size :: Num a => Tree b -> a -size (Leaf _) = 1 -size (Node l r) = size l + size r - -instance (Show a) => Show (Tree a) where - show (Leaf x) = show x - show (Node l r) = show l ++ " " ++ show r -- would be possible to write read for this? - -instance Eq (Tree a) where - (==) t1 t2 = (size t1) == (size t2) - -instance Ord (Tree a) where - compare t1 t2 = compare (size t1) (size t2) -``` - -### Enum and Bounded - -Class `Enum` defines operations on sequentially ordered types and it is a subclass of `Bounded` which defines just `minBound` and `maxBound` values. As you can see below, `Enum` describes 8 functions but only 2 are required (other will be derived based on that). Functions `toEnum` and `fromEnum` serve for specifying the order by numbering with `Int`s. - -```haskell -class Enum a where - succ :: a -> a - pred :: a -> a - toEnum :: Int -> a - fromEnum :: a -> Int - enumFrom :: a -> [a] - enumFromThen :: a -> a -> [a] - enumFromTo :: a -> a -> [a] - enumFromThenTo :: a -> a -> a -> [a] - {-# MINIMAL toEnum, fromEnum #-} -``` - -When you derive `Enum`, the order will be generated as left-to-right order of data constructors (without parameters, an enumeration consists of one or more nullary ones). Similarly, deriving `Bounded` will use the first and the last data constructor. - -Enumerations have also the `..` syntactic sugar. For example, `[1..10]` is translated to `enumFromThen 1 10` and `[1,5..100]` is translated to `enumFromThenTo` - ## FP in other languages Functional programming concepts that you learn in a pure functional language may be more or less applicable in other languages, as well, according to the concepts supported and how well they are implemented. Also, some languages provide serious functional constructs, but they are quite cumbersome syntactically, which repels their common usage (yes, we point to you, JavaScript ;-) @@ -790,16 +809,16 @@ int calculate(binOperation bo, const int operandA, const int operandB){ ## Task assignment -The homework to practice working with advanced functional patterns, operators, and typeclasses is in repository [MI-AFP/hw05](https://github.com/MI-AFP/hw05). +The homework to practice working with new types, list comprehensions, containers, and errors is in repository [MI-AFP/hw04](https://github.com/MI-AFP/hw04). ## Further reading -* [Haskell - Currying](https://wiki.haskell.org/Currying) -* [Haskell - Pointfree](https://wiki.haskell.org/Pointfree) -* [Haskell - Higher order function](https://wiki.haskell.org/Higher_order_function) -* [Haskell - Fold](https://wiki.haskell.org/Fold) -* [Learn You a Haskell for Great Good](http://learnyouahaskell.com) (chapters 3, 6, 8) -* [Haskell - Polymorphism](https://wiki.haskell.org/Polymorphism) -* [Typeclassopedia](https://wiki.haskell.org/Typeclassopedia) -* [Haskell - OOP vs type classes](https://wiki.haskell.org/OOP_vs_type_classes) -* [WikiBooks - Haskell: Classes and types](https://en.wikibooks.org/wiki/Haskell/Classes_and_types) +* [Haskell containers](https://haskell-containers.readthedocs.io/en/latest/) +* [Haskell - Handling errors in Haskell](https://wiki.haskell.org/Handling_errors_in_Haskell) +* [Haskell - error](https://wiki.haskell.org/Error) +* [8 ways to report errors in Haskell](http://www.randomhacks.net/2007/03/10/haskell-8-ways-to-report-errors/) + +[containers]: https://hackage.haskell.org/package/containers +[GHC]: https://www.haskell.org/ghc/ +[Hackage]: https://hackage.haskell.org +[Hoogle]: https://www.haskell.org/hoogle/ diff --git a/tutorials/04_types-errors.md b/tutorials/04_types-errors.md deleted file mode 100644 index 97aa0eb..0000000 --- a/tutorials/04_types-errors.md +++ /dev/null @@ -1,503 +0,0 @@ -# Types, containers, and errors - -## Evaluation - -First, we briefly get back to laziness in Haskell as mentioned in the previous lesson, because it is also related to lazy/strict types which will be discussed in this lesson. - -### List comprehensions - -Creation of lists using a constructor (or its syntactic sugar) is pretty straightforward, but there are two more interesting ways for that. First is used basically for basic ranges and it is called "dot dot notation". You have seen it already. It works with types that are instances of type class `Enum` (you can check within GHCi by `:info Enum`. You can specify start, step and end of the range (inclusive), but you need to be careful with floats and doubles because of their precision - the error cumulatively grows. - -``` -Prelude> [1..10] -[1,2,3,4,5,6,7,8,9,10] -Prelude> [0,5..20] -[0,5,10,15,20] -Prelude> ['a' .. 'z'] -"abcdefghijklmnopqrstuvwxyz" -Prelude> [1.0,1.05 .. 1.2] -[1.0,1.05,1.1,1.1500000000000001,1.2000000000000002] -``` - -A more flexible way is offered by [list comprehensions](https://wiki.haskell.org/List_comprehension). This concept/construct is nowadays used in many other programming languages, as well, such as Python. In "list" you first specify an expression with variables and then after pipe `|`, there are specifications of bindings and restrictions. It is also possible to define local names with `let`. - -``` -Prelude> [n*2+1 | n <- [1..5]] -[3,5,7,9,11] -Prelude> [(i, j) | i <- [1..5], j <- [0,1]] -[(1,0),(1,1),(2,0),(2,1),(3,0),(3,1),(4,0),(4,1),(5,0),(5,1)] -Prelude> [x | x <- [0..10], x `mod` 3 == 1, x /= 7] -[1,4,10] -Prelude> take 10 [(i, j) | i <- [1..5], let k=i-5, j <- [k..6]] -[(1,-4),(1,-3),(1,-2),(1,-1),(1,0),(1,1),(1,2),(1,3),(1,4),(1,5)] -``` - -### Lazy Haskell - -As we've already seen, Haskell has lazy non-strict evaluation strategy. It means that no expression is evaluated, unless the value is needed. One of the possibilities is creating infinite lists. You may use `undefined` for testing when the expression is evaluated. - -``` -Prelude> let x = 1:x -Prelude> take 10 x -[1,1,1,1,1,1,1,1,1,1] -Prelude> take 20 x -[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] -Prelude> x -[1,1,1,1,1,1,1,1,1,1,1,1,1,1,...^C Interrupted. -Prelude> [1,2..] -[1,2,3,4,5,6,7,8,9,10,11,12,13,14,...^C Interrupted. -Prelude> let x = undefined -Prelude> let y = 1 + x -Prelude> let z = y * 2 + 15 -Prelude> :type y -y :: Num a => a -Prelude> :type x -x :: a -Prelude> z -*** Exception: Prelude.undefined -CallStack (from HasCallStack): - error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err - undefined, called at :37:5 in interactive:Ghci23 -``` - -(For stopping output press CTRL+C in GHCi) - -### Strictness with types - -In the previous lesson, we touched the topic of enforcing strictness with `!` in patterns ([bang patterns](https://ocharles.org.uk/blog/posts/2014-12-05-bang-patterns.html)) and in function application with `$!` operator. Similarly, we can use `!` with type fields like this: - -```haskell -data MyType = MyConstr Int !Int - -data MyRec = MyRecConstr { xA :: Int - , xB :: !Int - } -``` - -For both cases it means that when data constructor is evaluated, it must fully evaluate ([weak head normal form](https://wiki.haskell.org/Weak_head_normal_form)) the second parameter, but the first one will stay unevaluated in a lazy way. All depends on language implementation in the used compiler. - -In order to achieve laziness, Haskell wraps all types with some additional information = *boxed types*. If you don't need laziness and other related properties, it is more efficient to use raw *unboxed types*. We will talk about that as a bonus or at the end of course in the *Performance* section. Now you just need to know roughly what is it about, because it is used in some textual types... - -## Textual types - -Textual types [(strings)](https://wiki.haskell.org/Strings) are kind of pain in Haskell. This is mostly because of its long legacy and also laziness/strictness trade-offs. However, as everything, they are not really insidious, just not convenient as they could be (and actually are in newer "Haskells", such as PureScript). - -### String - -[String](https://hackage.haskell.org/package/base/docs/Data-String.html) is the string type in the `base` package. It is just a type synonym for `[Char]`, so it comes with all properties of a [list](https://hackage.haskell.org/package/base/docs/Data-List.html), and as such, it is the most common one, especially for non-performance-sensitive applications. But when it comes to performance (and sometimes even Unicode behavior), then problems arise - `String` has big overhead in time and space. - -### Text - -[Data.Text](https://hackage.haskell.org/package/text/docs/Data-Text.html) from [text](https://hackage.haskell.org/package/text) package is a time and space-efficient implementation of Unicode text. You can convert between `Text` and `String` with functions `pack` and `unpack`. The `Data.Text` package exports functions with same names as there are for `String` (`head`, `length`, `map`, `replace`, etc.), so the advised import style is `import qualified Data.Text as T`. - -``` -Prelude> import qualified Data.Text as T -Prelude T> txt = T.pack "my effective text" -Prelude T> :type txt -txt :: T.Text -Prelude T> T.index txt 1 -'y' -Prelude T> T.replace "my" "your" txt - -:13:11: error: - • Couldn't match expected type ‘T.Text’ with actual type ‘[Char]’ - • In the first argument of ‘T.replace’, namely ‘"my"’ - In the expression: T.replace "my" "your" txt - In an equation for ‘it’: it = T.replace "my" "your" txt - -:13:16: error: - • Couldn't match expected type ‘T.Text’ with actual type ‘[Char]’ - • In the second argument of ‘T.replace’, namely ‘"your"’ - In the expression: T.replace "my" "your" txt - In an equation for ‘it’: it = T.replace "my" "your" txt -Prelude T> T.replace (T.pack "my") (T.pack "your") txt -"your effective text" -Prelude T> length txt - -:11:8: error: - • Couldn't match expected type ‘[a0]’ with actual type ‘T.Text’ - • In the first argument of ‘length’, namely ‘txt’ - In the expression: length txt - In an equation for ‘it’: it = length txt -Prelude T> T.length txt -17 -``` - -There is another variant of the Text package, which is [Data.Text.Lazy](https://hackage.haskell.org/package/text/docs/Data-Text-Lazy.html), which exports same operations and thanks to laziness, it can work with huge texts and it may provide better performance under the right circumstances. [Data.Text.Encoding](https://hackage.haskell.org/package/text/docs/Data-Text-Encoding.html) (and its lazy alternative) may be also useful. - -### ByteString - -Last of the types mentioned here is [Data.ByteString](https://hackage.haskell.org/package/bytestring/docs/Data-ByteString.html) from [bytestring](https://hackage.haskell.org/package/bytestring) package. Byte vectors are encoded as strict Word8 arrays of bytes and they are used for interoperability between Haskell and C or other lower-level situations. In many ways, the usage is similar to [text](https://hackage.haskell.org/package/text) package (again `pack` and `unpack`, same basic functions, `Lazy` alternative, and so on). Next, there is an option to use vectors with `Char8` instead of `Word8`, which works as Unicode subset (0-255) strings and it is used when working with pure ASCII string representations. - -``` -Prelude T> import Data.ByteString as B -Prelude T B> bstr = B.pack [97, 98, 99] -Prelude T B> bstr -"abc" -Prelude T B> index bstr 2 -99 -Prelude T B> B.map (+1) bstr -"bcd" - -Prelude T B> import qualified Data.ByteString.Char8 as C -Prelude T B C> C.pack "abc" -"abc" -Prelude T B C> B.pack "abc" - -:28:8: error: - • Couldn't match type ‘Char’ with ‘GHC.Word.Word8’ - Expected type: [GHC.Word.Word8] - Actual type: [Char] - • In the first argument of ‘pack’, namely ‘"abc"’ - In the expression: pack "abc" - In an equation for ‘it’: it = pack "abc" -Prelude T B C> cstr = C.pack "abc" -Prelude T B C> C.index cstr 2 -'c' -``` - -In other cases you need to use encoding to encode/decode bytes to/from text: - -``` -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 -"\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> index x 0 -197 -Prelude T B C E> index x 2 -105 -``` - -### OverloadedStrings - -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: - -```haskell -{-# LANGUAGE OverloadedStrings #-} - -module XY ... -``` - -In GHCi, the extension can be enabled using the `:set` directive: - -``` -Prelude> :set -XOverloadedStrings -``` - -After that, a string literal type can be inferred by its usage in the source code: - -``` -Prelude> import qualified Data.Text as T -Prelude T> :type "abc" -"abc" :: [Char] -Prelude T> :set -XOverloadedStrings -Prelude> :type "abc" -"abc" :: Data.String.IsString p => p -Prelude T> T.length "abc" -- no need to pack the literal any more! -3 -``` - -## Important "base" types - -We already know basic data types (from `base` package) such as [Data.Char](https://hackage.haskell.org/package/base/docs/Data-Char.html), [Bool](https://hackage.haskell.org/package/base/docs/Data-Bool.html), or [Data.Int](https://hackage.haskell.org/package/base/docs/Data-Int.html) and structures like [Data.List](https://hackage.haskell.org/package/base/docs/Data-List.html) and [Data.Tuple](https://hackage.haskell.org/package/base/docs/Data-Tuple.html) pretty well. But of course, there are more widely used types and we are going to know some more now. - -### Maybe - -In most programming languages, there is a notion of `null` or `nil` or even `None` value. Such a value is usable, but it leads often to undesired crashes of "Null pointer exception". As Haskell is type-safe, it does not allow such rogue surprises to happen, but instead deals with a possible "null" situation in a managed way. - -If we were to design such a solution, we may use ADTs like that: - -```haskell -data IntOrNull = I Int | Null -data StringOrNull = S String | Null -data ValueOrNull a = Value a | Null - -myDiv :: Int -> Int -> ValueOrNull Int -myDiv x 0 = Null -myDiv x y = Value (x `div` y) - -divString :: Int -> Int -> String -divString x y = case (myDiv x y) of - Null -> "Division by zero is not allowed!" - Value res -> "Result: " ++ (show res) -``` - -In Haskell, we have a pretty structure called `Maybe` which does exactly that for us and there are some functions helping with common usage. It is a very important structure and you will be dealing with it very often. You can find more about in the documentation of [Data.Maybe](https://hackage.haskell.org/package/base/docs/Data-Maybe.html). - -It is defined as: - -```haskell -data Maybe a = Nothing | Just a -``` - -``` -Prelude Data.Maybe> :type Just 10 -Just 10 :: Num a => Maybe a -Prelude Data.Maybe> :type Nothing -Nothing :: Maybe a -Prelude Data.Maybe> fromJust (Just 10) -10 -Prelude Data.Maybe> fromJust Nothing -*** Exception: Maybe.fromJust: Nothing -Prelude Data.Maybe> fromMaybe "default" Nothing -"default" -Prelude Data.Maybe> fromMaybe "default" (Just "something") -"something" -Prelude Data.Maybe> catMaybes [Just 6, Just 7, Nothing, Just 8, Nothing, Just 9] -[6,7,8,9] -``` - -```haskell --- Communicator interface -data Message = Message { msgSender :: String - , msgRecipient :: String - , msgMetadata :: [(String, String)] - , msgBody :: String - } - -sendAndReceive :: Communicator -> Message -> Maybe Message -sendAndReceive comm msg = sendSync comm msg -- some library "magic" - -printReceivedMessage :: Maybe Message -> String -printReceivedMessage Nothing = "Unknown error occured during communication." -printReceivedMessage (Just msg) = msgSender msg ++ ": " ++ msgBody msg - -myCommunicator = printReceivedMessage . sendAndReceive comm -``` - -### Either - -`Maybe` is also used to signal two types of results: an error (`Nothing`) and a success (`Just value`). However, it does not tell what is the error in case of `Nothing`. There is a standard type for such use cases, and it is called `Either`: - -```haskell -data Either a b = Left a | Right b -``` - -The `Left` variant holds an error value (such as a message) and the `Right` variant holds the success result value. There are again several utility functions available (see [Data.Either](https://hackage.haskell.org/package/base/docs/Data-Either.html)) - - -``` -Prelude Data.Either> :type Left 7 -Left 7 :: Num a => Either a b -Prelude Data.Either> :type Right "Message" -Right "Message" :: Either a [Char] -Prelude Data.Either> lefts [Left 7, Right "Msg1", Left 8, Right "Msg2"] -[7,8] -Prelude Data.Either> rights [Left 7, Right "Msg1", Left 8, Right "Msg2"] -["Msg1","Msg2"] -Prelude Data.Either> partitionEithers [Left 7, Right "Msg1", Left 8, Right "Msg2"] -([7,8],["Msg1","Msg2"]) -``` - -```haskell --- Communicator interface -data Message = Message { msgSender :: String - , msgRecipient :: String - , msgMetadata :: [(String, String)] - , msgBody :: String - } - -data CommError = Timeout | Disconnected | UnkownRecipient | IncorrectMetadata | UnknownError - deriving Show - -sendAndReceive :: Communicator -> Message -> Either CommError Message -sendAndReceive comm msg = sendSync comm msg -- some library "magic" - -printReceivedMessage :: Either CommError Message -> String -printReceivedMessage (Left err) = "Error occured during communication: " ++ show err -printReceivedMessage (Right msg) = msgSender msg ++ ": " ++ msgBody msg - -myCommunicator = printReceivedMessage . sendAndReceive comm -``` - -### Unit - -Although we said there is no `null`, `nil` or `None`, we still have one dummy value/type called "Unit" and it is designated as an empty tuple `()`. - -``` -Prelude> :info () -data () = () -- Defined in ‘GHC.Tuple’ -Prelude> :type () -() :: () -``` - -It is semantically more similar to `void` from other languages and you can use it wherever you don't want to use an actual type. For example, using `Either` to simulate `Maybe`, you could do `Either a ()`. For more about [unit type, read wikipedia](https://en.wikipedia.org/wiki/Unit_type). - -## Other containers - -As in other programming languages or programming theory, there are various types of containers - data types/structures, whose instances are collections of other objects. As for collections with an arbitrary number of elements, we talked about lists, which are simple to use and have a nice syntactic-sugar notation in Haskell. However, there are also other versatile types of containers available in the package [containers] and others, such as [array](https://hackage.haskell.org/package/array), [vector](https://hackage.haskell.org/package/vector), and more (use [Hoogle], [Hayoo], [Hackage]). - -### Sequence - -The `Seq a` is a type from [Data.Sequence](https://hackage.haskell.org/package/containers/docs/Data-Sequence.html) that represents a **finite** sequence of values of type `a`. Sequences are very similar to lists, working with sequences is not so different, but some operations are more efficient - constant-time access to both the front and the rear and Logarithmic-time concatenation, splitting, and access to any element. But in other cases, it can be slower than lists because of overhead created for making operations efficient. The size of a `Seq` must not exceed `maxBound::Int`! - -``` -Prelude> import Data.Sequence -Prelude Data.Sequence> seq1 = 1 <| 2 <| 15 <| 7 <| empty -Prelude Data.Sequence> seq1 -fromList [1,2,15,7] -Prelude Data.Sequence> :type seq1 -seq1 :: Num a => Seq a -Prelude Data.Sequence> 3 <| seq1 -fromList [3,1,2,15,7] -Prelude Data.Sequence> seq1 |> 3 -fromList [1,2,15,7,3] -Prelude Data.Sequence> seq1 >< (fromList [2, 3, 4]) -fromList [1,2,15,7,2,3,4] -Prelude Data.Sequence> sort seq1 -fromList [1,2,7,15] -``` - -### Set - -The `Set e` type represents a set of elements of type `e`. Most operations require that `e` be an instance of the `Ord` class. A `Set` is strict in its elements. If you know what is *set* in math and/or programming, you can be very powerful with them. - -``` -Prelude> import Data.Set -Prelude Data.Set> set1 = insert 4 $ insert 2 $ insert 0 $ singleton 2 -Prelude Data.Set> set1 -fromList [0,2,4] -Prelude Data.Set> delete 2 set1 -fromList [0,4] -Prelude Data.Set> delete 3 set1 -fromList [0,2,4] -Prelude Data.Set> mem -member mempty -Prelude Data.Set> member 4 set1 -True -Prelude Data.Set> member (-6) set1 -False -Prelude Data.Set> Data.Set.filter (>3) set1 -fromList [4] -Prelude Data.Set> set2 = insert 5 (insert 3 (singleton 2)) -Prelude Data.Set> set2 -fromList [2,3,5] -Prelude Data.Set> set1 -fromList [0,2,4] -Prelude Data.Set> intersection set1 set2 -fromList [2] -Prelude Data.Set> union set1 set2 -fromList [0,2,3,4,5] -``` - -There is an efficient implementation of integer sets, which uses big-endian Patricia trees (works better mainly with union and intersection). Use qualified import like `import qualified Data.IntSet as IntSet` to work with it. - -### Map - -The `Map k v` type represents a finite map (sometimes called a dictionary) from keys of type `k` to values of type `v`. A Map is strict in its keys but lazy in its values (by default we use [Data.Map.Lazy](https://hackage.haskell.org/package/containers/docs/Data-Map-Lazy.html). You may use [Data.Map.Strict](https://hackage.haskell.org/package/containers/docs/Data-Map-Strict.html) instead if you will eventually need all the values stored and/or the stored values are not so complicated to compute (no big advantage of laziness). - -``` -Prelude> import Data.Map -Prelude Data.Map> map1 = insert "suchama4" "Marek Suchanek" (singleton "perglr" "Robert Pergl") -Prelude Data.Map> map1 ! "suchama4" -"Marek Suchanek" -Prelude Data.Map> map1 ! "suchamar" -"*** Exception: Map.!: given key is not an element in the map -CallStack (from HasCallStack): - error, called at ./Data/Map/Internal.hs:610:17 in containers-0.5.11.0-K2TDqgYtGUcKxAY1UqVZ3R:Data.Map.Internal -Prelude Data.Map> map1 !? "suchamar" -Nothing -Prelude Data.Map> map1 !? "suchama4" -Just "Marek Suchanek" -Prelude Data.Map> size map1 -2 -Prelude Data.Map> delete "suchama4" map1 -fromList [("perglr","Robert Pergl")] -Prelude Data.Map> delete "suchamar" map1 -fromList [("perglr","Robert Pergl"),("suchama4","Marek Suchanek")] -Prelude Data.Map> map2 = insert "suchama4" "Marek Suchanek" (singleton "stengvac" "Vaclav Stengl") -Prelude Data.Map> map2 = insert "suchama4" "Marek Sushi Suchanek" (singleton "stengvac" "Vaclav Stengl") -Prelude Data.Map> union map1 map2 -fromList [("perglr","Robert Pergl"),("stengvac","Vaclav Stengl"),("suchama4","Marek Suchanek")] -Prelude Data.Map> union map2 map1 -fromList [("perglr","Robert Pergl"),("stengvac","Vaclav Stengl"),("suchama4","Marek Sushi Suchanek")] -Prelude Data.Map> intersection map1 map2 -fromList [("suchama4","Marek Suchanek")] -``` - -Again, there is an efficient implementation of maps, where the keys are of `Int`. It uses same mechanisms as `Data.IntSet` - use `import qualified Data.IntMap as IntMap`. - -### Graph and Tree - -Finally, [containers] specify also [Data.Tree](https://hackage.haskell.org/package/containers/docs/Data-Tree.html) and [Data.Graph](https://hackage.haskell.org/package/containers/docs/Data-Graph.html), both in a very generic manner. If you ever need to work with trees or graphs, it is convenient to use those instead of reinventing the wheel yourself. - -## Handling errors - -As we saw, a very elegant way way how to handle errors is using `Maybe` or `Either` types. This is a preferred way with obvious advantages, however, in practice, it may still come to a more explosive situation. - -### error - -`error` is a special function which stops execution with given message: - -``` -Prelude> error "Stop now" -*** Exception: Stop now -CallStack (from HasCallStack): - error, called at :1:1 in interactive:Ghci1 -``` - -There is another quite similar one - `errorWithoutStackTrace`: - -``` -Prelude> errorWithoutStackTrace "Stop now without stack trace" -*** Exception: Stop now without stack trace -``` - -It is obviously even worse than just `error` because you somewhere deep in your code say something about rendering the error... - -### undefined - -Special case of error is that something is `undefined` and it does not accept any message: - -``` -Prelude> undefined -*** Exception: Prelude.undefined -CallStack (from HasCallStack): - error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err - undefined, called at :5:1 in interactive:Ghci1 -``` - -Semantically, it can be used where the value is not defined (for example when you want to divide by zero). Sometimes you can see it used as a basic placeholder with meaning "Not implemented yet". For such things, you can use custom `error` or some specialized package like [Development.Placeholders](hackage.haskell.org/package/placeholders/docs/Development-Placeholders.html), which are more suitable. - -### throw, try and catch - -We have `throw`, `try` and `catch`, but those are functions - not keywords! - -``` -Prelude> import Control.Exception -Prelude Control.Exception> :type try -try :: Exception e => IO a -> IO (Either e a) -Prelude Control.Exception> :type throw -throw :: Exception e => e -> a -Prelude Control.Exception> :type catch -catch :: Exception e => IO a -> (e -> IO a) -> IO a -``` - -If you are interested you can read the documentation of [Control.Exception](https://hackage.haskell.org/package/base/docs/Control-Exception.html), however, exceptions are considered an anti-pattern in Haskell and you should always try to deal with potential errors in a more systematic way using types. We will slightly get back to these after getting the notion of Monads. - -## Task assignment - -The homework to practice working with new types, list comprehensions, containers, and errors is in repository [MI-AFP/hw04](https://github.com/MI-AFP/hw04). - -## Further reading - -* [Oh my laziness](http://alpmestan.com/posts/2013-10-02-oh-my-laziness.html) -* [Haskell - list comprehension](https://wiki.haskell.org/List_comprehension) -* [Haskell - Lazy evaluation](https://wiki.haskell.org/Lazy_evaluation) -* [Haskell String Types](http://www.alexeyshmalko.com/2015/haskell-string-types/) -* [Untangling Haskells strings](https://mmhaskell.com/blog/2017/5/15/untangling-haskells-strings) -* [Haskell containers](https://haskell-containers.readthedocs.io/en/latest/) -* [Haskell - Handling errors in Haskell](https://wiki.haskell.org/Handling_errors_in_Haskell) -* [Haskell - error](https://wiki.haskell.org/Error) -* [8 ways to report errors in Haskell](http://www.randomhacks.net/2007/03/10/haskell-8-ways-to-report-errors/) - -[containers]: https://hackage.haskell.org/package/containers -[GHC]: https://www.haskell.org/ghc/ -[Hackage]: https://hackage.haskell.org -[Hayoo]: https://hayoo.fh-wedel.de -[Hoogle]: https://www.haskell.org/hoogle/ diff --git a/tutorials/07_common-typeclasses-1.md b/tutorials/05_typeclasses.md similarity index 62% rename from tutorials/07_common-typeclasses-1.md rename to tutorials/05_typeclasses.md index 1adf385..a4d678e 100644 --- a/tutorials/07_common-typeclasses-1.md +++ b/tutorials/05_typeclasses.md @@ -1,16 +1,237 @@ -# Common typeclasses I +# Typeclasses - custom and predefined + +## Typeclasses + +Typeclass is a class of types with a definition of common functions for all instances of that class. Please note that the term "class" here means a [mathematical class in the set theory](https://en.wikipedia.org/wiki/Class_(set_theory)), not an [object-oriented class](https://en.wikipedia.org/wiki/Class_(computer_programming))!. + +Typeclasses represent a powerful means for polymorphism in a strong static type system. They can be related to interfaces in Java or protocols in Clojure, however, they are more powerful. + +### Kinds + +In the type theory, a kind is the type of a type constructor or, less commonly, the type of a higher order type operator. A kind system is essentially a simply-typed lambda calculus 'one level up' from functions to their types, endowed with a primitive type, denoted `*` and called 'type', which is the kind of any (monomorphic) data type. Simply you can observe this with GHCi and `:kind` command on various types. For example kind `* -> *` tells that you need to specify one type argument to create a type with kind `*` so you can have values of it. + +``` +Prelude> :kind Integer +Integer :: * +Prelude> :kind Maybe +Maybe :: * -> * +Prelude> :kind Either +Either :: * -> * -> * +Prelude> :kind (Either Integer) +(Either Integer) :: * -> * +Prelude> :kind (Either Integer String) +(Either Integer String) :: * +``` + +### Polymorphism + +[Polymorphism](https://en.wikipedia.org/wiki/Polymorphism_(computer_science)) (from Greek πολύς, polys, "many, much" and μορφή, morphē, "form, shape") is the provision of a single interface to entities of different types. A polymorphic type is one whose operations can also be applied to values of some other type, or types. Polymorphism is usually connected with object-oriented programming today, however, there are even more powerful types of polymorphism in functional programming languages, such as Haskell or Clojure. + +We already used type classes and type variables - the basic enablers of polymorphism in Haskell. **Parametric polymorphism** refers to the situation when the type of a value contains one or more (unconstrained) type variables, so that the value may adopt any type that results from substituting those variables with concrete types. It is when the type variable *is not constrained* by some type class. For example, length of a list `[a]` works for any type `a`. In this tutorial, there was the `map` function with type `(a -> b) -> [a] -> [b]` - also a parametric polymorphism. This type of polymorphism doesn't know anything about the type which it will be used with. The behaviour is abstracted regardless of a concrete type. This is a somewhat limiting but extremely useful property known as versatility. + +**Ad-hoc polymorphism** refers to the situation when a value is able to adopt any one of a defined set of types because it, or a value it uses, has been given a separate definition for each of those types. This is a situation when the type variable *is constrained* by some type class. Thanks to that extra information about the given *still-generic* type, it is possible to use behaviour defined during class instantiation. Haskell even allows class instances to be defined for types which are themselves polymorphic - you can make your implementation of an arbitrary list an instance of some class. This polymorphism is not only about functions, but also about values - recall `Bounded` class with `minBound` and `maxBound` values which are different for each instance. + +There are some other types of polymorphism that are implemented in some extensions to Haskell, e.g. [rank-N types](https://wiki.haskell.org/Rank-N_types) and [impredicative types](https://wiki.haskell.org/Impredicative_types). But there are also types of polymorphism that are not supported in Haskell, for example, subtyping and inclusion polymorphism. + +### Own typeclass and instance + +There are some commonly used typeclasses and their instances available and you can create your own, as well. You need to use keyword `class` to defined functions for that typeclass and also optionally a class inheritance, i.e. a base class, which is extended. There can be even more, so the inheritance forms [lattices](https://commons.wikimedia.org/wiki/File:Base-classes.svg). + +Contrary to Java interfaces, apart from function declarations, Haskell typeclasses can also contain function implementations. + +Next, you create typeclass instances using the keyword `instance`, which means you define or (re-define) functions for that class. + +```haskell +-- from https://en.wikibooks.org/wiki/Haskell/Classes_and_types#A_concerted_example +-- Location, in two dimensions. +class Located a where + getLocation :: a -> (Int, Int) + +class (Located a) => Movable a where + setLocation :: (Int, Int) -> a -> a + +-- An example type, with accompanying instances. +data NamedPoint = NamedPoint + { pointName :: String + , pointX :: Int + , pointY :: Int + } deriving (Show) + +instance Located NamedPoint where + getLocation p = (pointX p, pointY p) + +instance Movable NamedPoint where + setLocation (x, y) p = p { pointX = x, pointY = y } + +-- Moves a value of a Movable type by the specified displacement. +-- This works for any movable, including NamedPoint. +move :: (Movable a) => (Int, Int) -> a -> a +move (dx, dy) p = setLocation (x + dx, y + dy) p + where + (x, y) = getLocation p +``` + +## Basic typeclasses in detail + +### Deriving + +We have already used the keyword `deriving` many times. It is kind of magic which will automatically derive an instance of desired typeclass(es) so you don't have to write functions on your own. + +You can derive only built-in typeclasses: + +* `Eq` = (in)equality based on arguments +* `Ord` = `compare`, `min`, `max` and comparison operators +* `Show` = to `String` conversion +* `Read` = from `String` conversion +* `Enum` = enumerations only (no arguments), list `..` syntax +* `Bounded` = only for enumerations or just one arguments, `minBound` and `maxBound` + +You can use `deriving` for your own classes, but you need to put some (advanced) effort in it by using [Data.Derive](https://hackage.haskell.org/package/derive) and [Template Haskell](https://wiki.haskell.org/Template_Haskell). + +### Read and Show + +If you derive default implementation of instances for `Show` and `Read` the string representing the data is actually the same as you would write it in Haskell code: + +``` +Prelude> data Tree a = Leaf a | Node (Tree a) (Tree a) deriving (Show, Read) +Prelude> +Prelude> myTree = Node (Leaf 8) (Node (Leaf 70) (Leaf 15)) +Prelude> show myTree +"Node (Leaf 8) (Node (Leaf 70) (Leaf 15))" +Prelude> read "Node (Leaf 5) (Leaf 100)" +*** Exception: Prelude.read: no parse +Prelude> (read "Node (Leaf 5) (Leaf 100)") :: Tree Int +Node (Leaf 5) (Leaf 100) +``` + +```haskell +class Show a where + showsPrec :: Int -> a -> ShowS + show :: a -> String + showList :: [a] -> ShowS + {-# MINIMAL showsPrec | show #-} + -- Defined in ‘GHC.Show’ +``` + +When you make own `read` and `show`, you should ensure that after using `read` on a string produced by `show`, you will get the same data: + +```haskell +data Tree a = Leaf a | Node (Tree a) (Tree a) + +size :: Num a => Tree b -> a +size (Leaf _) = 1 +size (Node l r) = size l + size r + +instance (Show a) => Show (Tree a) where + show (Leaf x) = show x + show (Node l r) = show l ++ " " ++ show r -- would be possible to write read for this? +``` + + `show` is (quite often) abused to represent fancy string representations, but it is adviced to name such functions differently (such as `myShow`), or to use a pretty-printing library such as [Text.PrettyPrint](https://hackage.haskell.org/package/pretty-1.1.3.6/docs/Text-PrettyPrint.html), [pretty](https://hackage.haskell.org/package/pretty), [pretty-simple](https://hackage.haskell.org/package/pretty-simple) or [prettyprinter](https://hackage.haskell.org/package/prettyprinter). + +Typeclass `Read` is a bit more complex. If you want to make your own implementation, you need to write a parser. Parsing will be covered later on during the course. Basically, it tries to convert `String` to `a` and return it together with the remaining `String`. + +```haskell +class Read a where + readsPrec :: Int -> ReadS a + readList :: ReadS [a] + GHC.Read.readPrec :: Text.ParserCombinators.ReadPrec.ReadPrec a + GHC.Read.readListPrec :: Text.ParserCombinators.ReadPrec.ReadPrec [a] + {-# MINIMAL readsPrec | readPrec #-} + -- Defined in `GHC.Read' +``` + +### Numerics + +For numbers, there are several built-in typeclasses making numeric computing more flexible: + +* `Num` + * `Real` + * `Integral` + * `RealFrac` + * `Fractional` + * `Floating` + * `RealFloat` (subclass of `Floating` and `RealFrac`) + +If you don't need to explicitly specify the type of number, use the typeclass constraint in the declaration of function type. This is a general rule of thumb: be as much generic, as possible. + +### Comparison + +There are two basic typeclasses - `Eq` and its subclass `Ord` that specify comparisons. + +```haskell +class Eq a where + (==) :: a -> a -> Bool + (/=) :: a -> a -> Bool + {-# MINIMAL (==) | (/=) #-} + -- Defined in ‘GHC.Classes’ + +class Eq a => Ord a where + compare :: a -> a -> Ordering + (<) :: a -> a -> Bool + (<=) :: a -> a -> Bool + (>) :: a -> a -> Bool + (>=) :: a -> a -> Bool + max :: a -> a -> a + min :: a -> a -> a + {-# MINIMAL compare | (<=) #-} + -- Defined in ‘GHC.Classes’ +``` + +You can again implement your own instances of those classes: + +```haskell +data Tree a = Leaf a | Node (Tree a) (Tree a) + +size :: Num a => Tree b -> a +size (Leaf _) = 1 +size (Node l r) = size l + size r + +instance (Show a) => Show (Tree a) where + show (Leaf x) = show x + show (Node l r) = show l ++ " " ++ show r -- would be possible to write read for this? + +instance Eq (Tree a) where + (==) t1 t2 = (size t1) == (size t2) + +instance Ord (Tree a) where + compare t1 t2 = compare (size t1) (size t2) +``` + +### Enum and Bounded + +Class `Enum` defines operations on sequentially ordered types and it is a subclass of `Bounded` which defines just `minBound` and `maxBound` values. As you can see below, `Enum` describes 8 functions but only 2 are required (other will be derived based on that). Functions `toEnum` and `fromEnum` serve for specifying the order by numbering with `Int`s. + +```haskell +class Enum a where + succ :: a -> a + pred :: a -> a + toEnum :: Int -> a + fromEnum :: a -> Int + enumFrom :: a -> [a] + enumFromThen :: a -> a -> [a] + enumFromTo :: a -> a -> [a] + enumFromThenTo :: a -> a -> a -> [a] + {-# MINIMAL toEnum, fromEnum #-} +``` + +When you derive `Enum`, the order will be generated as left-to-right order of data constructors (without parameters, an enumeration consists of one or more nullary ones). Similarly, deriving `Bounded` will use the first and the last data constructor. + +Enumerations have also the `..` syntactic sugar. For example, `[1..10]` is translated to `enumFromThen 1 10` and `[1,5..100]` is translated to `enumFromThenTo` + +## Mathematical Typeclasses Now we are going to spend some time with predefined and important typeclasses that capture important concepts in Haskell that are widely used in many projects. Typeclass always says something about the structure of the type and what you can do with that. Also, there are some laws and it is very tightly related to math -- specifically the algebra and the category theory. After learning common typeclasses, it is not just easier to use them and understand a code written by other developers, but it also helps with designing own custom typeclasses. You can always find out more about a typeclass and instances with GHCi `:info` command. -## Intro: Mathematical foundations +### Intro: Mathematical foundations The relation between math and Haskell is very strong. You can observe it everywhere. Haskell functions are very similar to mathematical functions when you talk about their type, definitions with `let` and `where` keywords, guards with `otherwise`, function compositions, and so on. In this tutorial, we are going to see this relation even more -- with typeclasses that come from the mathematical world. You should be already familiar with [basic abstract algebra](https://en.wikipedia.org/wiki/Algebra#Abstract_algebra) (esp. algebraic structures with a single binary operation). When getting into the math, you should know that Haskell is based not just on the basic algebra, set theory, and logic, but also on the category theory. In order to sometimes mention the relation, we will briefly explain what it is about. If you want to know more, please refer to some mathematical tutorials on your own. -### Category theory +#### Category theory Category theory is a higher abstraction level over *Monoid*, *Semigroup*, and similar algebraic structures. Actually, it is so abstract that it can describe so many things ... that it is very hard to comprehend. The "best practice" among programmers is to learn it bit by bit and return to it from time to time until things "click" together. This "click" means putting together the theory and its practical applications. @@ -26,7 +247,7 @@ There are many categories, for example, **Set** category has all possible sets a 2. the category needs to be closed under the composition operation (i.e., for all applies *h = f ∘ g ⇒ h ∈ C*), 3. for every object *A ∈ ob(C)* there is an identity function *idA: A → A*, *idA ∈ hom(C)*. -### The Hask category +#### The Hask category In Haskell, we have the **Hask** category where: @@ -36,7 +257,7 @@ In Haskell, we have the **Hask** category where: The identity function is for every *o ∈ ob(Hask)* the polymorphic `id` function. The associativity of the composition is assured and in *hom(C)* there are all the functions, even those created by composition. That's it for now -- now we will show some typeclasses, their laws and come back to **Hask** when necessary... -## Semigroup, Monoid, ... +### Semigroup, Monoid, ... `Monoid` is the most simple typeclass we will learn. You can recall the [monoid](https://en.wikipedia.org/wiki/Monoid) from the algebra -- it is an algebraic structure with one binary operation that is associative and there is also one identity element. The same goes for Haskell -- the operation is called `mappend` and the identity is `mempty` (the first letter `m` if for **m**onoid). @@ -93,7 +314,7 @@ Prelude Data.Monoid> mconcat (map Product [1..5]) Product {getProduct = 120} ``` -### Example: textual types +#### Example: textual types One of the very practical usages of `mappend` is string concatenation, which is independent of its concrete implementation: @@ -117,7 +338,7 @@ t1 <> ", " <> t2 -- works the same for text! Here, obviously `mappend` is string concatenation and `mempty = ""`. -### Example: Maybe +#### Example: Maybe From `:info Monoid`, we can see that `Maybe a` is an instance of `Monoid` iff (=if and only if) `a` is an instance of `Monoid`. Then, the `(<>)` is "propagated" inside and obviously the identity is `Nothing`. @@ -136,11 +357,11 @@ Prelude Data.Maybe Data.Semigroup> mempty :: Maybe String Nothing ``` -### Verify the laws +#### Verify the laws As said, there are some laws (identity and associativity in this case) that should be valid, but the compiler cannot enforce it. Whenever you introduce your own instance of `Monoid` or other structure with some laws that are expected to be valid, use tests to prove it. One way is to write the properties on your own. The second and better one is to use [checkers](https://hackage.haskell.org/package/checkers), where many standard properties are prepared for you... -### Others from basic algebra +#### Others from basic algebra Apart from basic `Monoid` from algebra, there are also other variants. You might find interesting to learn more about: @@ -150,7 +371,7 @@ Apart from basic `Monoid` from algebra, there are also other variants. You might It is possible to write own instances of `Monoid` or other typeclasses. However, mind that compiler *won't* check if laws are valid in your instance. For such checks, you can use testing frameworks (esp. property testing), which will be covered later on. -## Functor +### Functor A functor is a way to apply a function to values inside some structure, while the structure remains intact. For example, if you want to change values in a list, tree or in Either without dealing with complexity and internal structure. @@ -206,7 +427,7 @@ Just 2 These examples might seem a bit too simple, but you can have any instance of `Functor` without knowing the structure and implementation of it and affect what is inside by these two (four if counting also flipped) simple operators. -### Lifting +#### Lifting [Lifting](https://wiki.haskell.org/Lifting) is a concept that allows you to transform a function into a corresponding function within another (usually a more general) setting. Lifting is again a concept taken from mathematics and category theory (see [wikipedia](https://en.wikipedia.org/wiki/Lift_(mathematics)). @@ -237,7 +458,7 @@ plusPoints :: Point2D Int -> Point2D Int -> Point2D Int plusPoints = liftF2 (+) ``` -### Functors on Hask category +#### Functors on Hask category In mathematics, a functor is a type of mapping between categories arising in category theory. Functors can be thought of as homomorphisms between categories. In the category of small categories, functors can be thought of more generally as morphisms. ([wikipedia](https://en.wikipedia.org/wiki/Functor)) @@ -260,7 +481,7 @@ fmap id == id fmap (f . g) = fmap f . fmap g ``` -## Applicative +### Applicative Another important typeclass is [Control.Applicate](https://hackage.haskell.org/package/base/docs/Control-Applicative.html). Notice that it is not "Data" anymore, but "Control" instead! It is an intermediate structure between a `Functor` and a `Monad`. It is simpler than `Monad`, not so powerful, but sufficient in many use cases, and also easier to understand - it is **Applicative functor**. @@ -337,11 +558,11 @@ Prelude Control.Applicative> liftA2 (+) (Right 15) (Right 7) Right 22 ``` -### Actions vs. functions +#### Actions vs. functions In Haskell terminology, we call `Functor f => f a` an **action**. Actions have the power to do some side effect but not necessarily (e.g., `Just "no effect"`). For example, `liftA2` is described as a function that lifts a binary function to actions. Special sort of actions are I/O actions that do something with input and output, but there can be also other actions making side effects. -## Monad +### Monad The most famous (and [scary](https://camo.githubusercontent.com/f2c3667a2cdf19c0cf203fad44c81d197c4cd740/68747470733a2f2f692e696d67666c69702e636f6d2f317a6e707a622e6a7067) :-)) typeclass for Haskell students is [Control.Monad](https://hackage.haskell.org/package/base/docs/Control-Monad.html). It defines basic operations over a monad, a term from category theory. From the perspective of a Haskell programmer, however, it is best to think of a monad as an "abstract datatype of actions". Haskell's `do` expressions provide a convenient syntax for writing monadic expressions. This time we will start Monads (operations, laws, basic behavior, etc.) and next time we will get deeper with some more practical use-cases. @@ -384,16 +605,16 @@ Prelude Control.Monad> (Left "err") >>= (\x -> return (x+10)) Left "err" ``` -### Do syntax +#### Do syntax Monads in Haskell are so useful that they got their own special syntax called `do` notation. We first introduced it way back in the "Simple input and output" chapter. There, we used it to sequence input/output operations, but we hadn't introduced monads yet. Now, we can see that IO is yet another monad. Imagine we have a sequence operation like this: ```haskell -Prelude> Just 7 >>= (\x -> Just (show x ++ "!")) +Prelude> Just 7 >>= (\x -> Just (show x ++ "!")) Just "7!" -Prelude> Just 7 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y))) +Prelude> Just 7 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y))) Just "7!" Prelude> print 3 >> print 5 >> print 7 3 @@ -404,11 +625,11 @@ Prelude> print 3 >> print 5 >> print 7 Haskell provides the `do` notation so we can avoid writing all the lambdas: ```haskell -foo :: Maybe String -foo = do - x <- Just 7 - y <- Just "!" - Just (show x ++ y) +foo :: Maybe String +foo = do + x <- Just 7 + y <- Just "!" + Just (show x ++ y) ``` or @@ -432,7 +653,7 @@ main = do print answer -- let and binding cannot be the last in do! ``` -### Loops +#### Loops You might hear that "do" provides an imperative-like way of programming... That's true but it is really just *imperative-like* from a visual point of view, it is still purely functional! But even in math and functions, you can introduce something like `for` or `while` loops. When you want to compute some result like factorial, sum, length of a list it is natural to use recursion. With actions, it might give more sense to use loops (even when they are actually done by recursion): @@ -461,91 +682,27 @@ main = do For other loops, visit [Control.Monad.Loops](https://hackage.haskell.org/package/monad-loops/docs/Control-Monad-Loops.html) and this [article](https://conscientiousprogrammer.com/blog/2015/12/11/24-days-of-hackage-2015-day-11-monad-loops-avoiding-writing-recursive-functions-by-refactoring/). -### Monads in category theory +#### Monads in category theory Again, monad comes from math and more specifically from category theory. A monad is a special type of functor, from a category to that same category (i.e., it is *endofunctor*), that supports some additional structure. Monad is a functor *M: C → C* with two morphisms for every object *X* from *C*: 1. *unit: X → M(X)* ~ `return :: Monad m => a -> m a` 2. *join: M(M(X)) → M(X)* ~ `(>>=) :: Monad m => m a -> (a -> m b) -> m b` -## IO - What is it? - -Haskell separates pure functions from computations where side effects must be considered by encoding those side effects as values of a particular type. Specifically, a value of type (IO a) is an action, which executed produces a value of type a. - -Some examples: - -```haskell -getLine :: IO String -putStrLn :: String -> IO () -- note that the result value is an empty tuple. -randomRIO :: (Random a) => (a,a) -> IO a -``` - -We tried to play with IO last time, but what is it? - -```haskell -Prelude> :info IO -newtype IO a - = GHC.Types.IO (GHC.Prim.State# GHC.Prim.RealWorld - -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #)) - -- Defined in ‘GHC.Types’ -instance Monad IO -- Defined in ‘GHC.Base’ -instance Functor IO -- Defined in ‘GHC.Base’ -instance Applicative IO -- Defined in ‘GHC.Base’ -instance Monoid a => Monoid (IO a) -- Defined in ‘GHC.Base’ -``` - -It is instance of `Monad`, but also `Functor`, `Aplicative`, and `Monoid` (iff `a` is also `Monoid`): - -```haskell -import System.Random -import Control.Applicative - -main0 :: IO () -main0 = mempty - -main1 :: IO () -main1 = putStrLn "a" `mappend` putStrLn "b" - -main2 :: IO () -main2 = mconcat (map print [1..5]) - -main3 :: IO () -main3 = do - rname <- reverse <$> getLine -- fmap reverse getLine - print rname - -main4 :: IO () -main4 = print 1 *> print 2 *> print 3 - -main5 :: IO () -main5 = print 1 <* print 2 <* print 3 - -main6 :: IO () -main6 = do - res <- (+) <$> randomInt <*> randomInt - print res - where randomInt = randomRIO (1, 10) :: IO Integer - -main7 :: IO () -main7 = do - res <- liftA2 (\x y -> x + read y) randomInt getLine - print res - where randomInt = randomRIO (1, 10) :: IO Integer -``` - -A lot of confusion comes from ideas such as "Monad is IO", "To do something impure I need a monad", "Monad brings imperative style to FP", or "Monad is something hard and weird". No, `Monad` is just a type class with defined operations and laws, just as `Monoid` (so pretty simple, right?!). IO actions manipulate and output, this is their essence. And BY THE WAY, they are (very conveniently) an instance of `Monad`, `Applicative`, and `Functor`. Those allow you to do some pure composition and other tricks with `IO` type and actions. A great and detailed explanation can be found on [HaskellWiki - IO inside](https://wiki.haskell.org/IO_inside). -## The task assignment +## Task assignment -The homework to practice typeclasses from this tutorial is in repository [MI-AFP/hw07](https://github.com/MI-AFP/hw07). +The homework to practice working with typeclasses is in repository [MI-AFP/hw05](https://github.com/MI-AFP/hw05). ## Further reading +* [Haskell: Polymorphism](https://wiki.haskell.org/Polymorphism) +* [Typeclassopedia](https://wiki.haskell.org/Typeclassopedia) +* [Haskell: OOP vs type classes](https://wiki.haskell.org/OOP_vs_type_classes) +* [WikiBooks Haskell: Classes and types](https://en.wikibooks.org/wiki/Haskell/Classes_and_types) * [Functors, Applicatives, And Monads In Pictures](http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html) * [Haskell and Category Theory](https://en.wikibooks.org/wiki/Haskell/Category_theory) * [Category Theory for Programmers by Bartosz Milewski](https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface) -* [LYAH - Functors, Applicative Functors and Monoids](http://learnyouahaskell.com/functors-applicative-functors-and-monoids) -* [LYAH - A Fistful of Monads](http://learnyouahaskell.com/a-fistful-of-monads) -* [Haskell - Typoclassopedia](https://wiki.haskell.org/Typeclassopedia) -* [Haskell - Monad](https://wiki.haskell.org/Monad) -* [Haskell - IO Monad](https://wiki.haskell.org/Introduction_to_IO) +* [LYAH: Functors, Applicative Functors and Monoids](http://learnyouahaskell.com/functors-applicative-functors-and-monoids) +* [LYAH: A Fistful of Monads](http://learnyouahaskell.com/a-fistful-of-monads) +* [Haskell: Monad](https://wiki.haskell.org/Monad) diff --git a/tutorials/08_common-typeclasses-2.md b/tutorials/06_io-exc-typeclasses.md similarity index 58% rename from tutorials/08_common-typeclasses-2.md rename to tutorials/06_io-exc-typeclasses.md index 7240301..17f30b0 100644 --- a/tutorials/08_common-typeclasses-2.md +++ b/tutorials/06_io-exc-typeclasses.md @@ -1,8 +1,309 @@ -# Common typeclasses 2 +# IO, Exceptions, and More Typeclasses -In this tutorial, we will take a brief look at few more advanced typeclasses that you might want to use in some projects. They are not described in high detail, but just in an introductory manner, so when you encouter some problem - you should know what you can use and learn specific details for your case. +In this tutorial, we will take a brief look at IO including exceptions and then at few more advanced typeclasses that you might want to use in some projects. They are not described in high detail, but just in an introductory manner, so when you encouter some problem - you should know what you can use and learn specific details for your case. -## Foldable +## Working with IO + +When you need to incorporate input and output (CLI, files, sockets, etc.), you bring impureness into your program. Obviously, IO brings side effects (it interacts with the environment and changes the global state). It can be a bit complicated and so we won't go deep into theory this time and instead, we will just show how to use it. Theoretical part will be covered in the future. + +```haskell +Prelude> :info IO +newtype IO a + = GHC.Types.IO (GHC.Prim.State# GHC.Prim.RealWorld + -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #)) + -- Defined in ‘GHC.Types’ +instance Monad IO -- Defined in ‘GHC.Base’ +instance Functor IO -- Defined in ‘GHC.Base’ +instance Applicative IO -- Defined in ‘GHC.Base’ +instance Monoid a => Monoid (IO a) -- Defined in ‘GHC.Base’ +``` + +It is instance of `Monad`, but also `Functor`, `Aplicative`, and `Monoid` (iff `a` is also `Monoid`): + +```haskell +import System.Random +import Control.Applicative + +main0 :: IO () +main0 = mempty + +main1 :: IO () +main1 = putStrLn "a" `mappend` putStrLn "b" + +main2 :: IO () +main2 = mconcat (map print [1..5]) + +main3 :: IO () +main3 = do + rname <- reverse <$> getLine -- fmap reverse getLine + print rname + +main4 :: IO () +main4 = print 1 *> print 2 *> print 3 + +main5 :: IO () +main5 = print 1 <* print 2 <* print 3 + +main6 :: IO () +main6 = do + res <- (+) <$> randomInt <*> randomInt + print res + where randomInt = randomRIO (1, 10) :: IO Integer + +main7 :: IO () +main7 = do + res <- liftA2 (\x y -> x + read y) randomInt getLine + print res + where randomInt = randomRIO (1, 10) :: IO Integer +``` + +A lot of confusion comes from ideas such as "Monad is IO", "To do something impure I need a monad", "Monad brings imperative style to FP", or "Monad is something hard and weird". No, `Monad` is just a type class with defined operations and laws, just as `Monoid` (so pretty simple, right?!). IO actions manipulate and output, this is their essence. And BY THE WAY, they are (very conveniently) an instance of `Monad`, `Applicative`, and `Functor`. Those allow you to do some pure composition and other tricks with `IO` type and actions. A great and detailed explanation can be found on [HaskellWiki - IO inside](https://wiki.haskell.org/IO_inside). + +### The main and gets + puts + +If you know C/C++, Python, or other programming languages, you should be familiar with "main". As in other languages, `main` is defined to be the entry point of a Haskell program. For Stack projects, it is located in a file inside `app` directory and can be defined in `package.yaml` in `executables` section (it is possible to have multiple entrypoints per program). The type of `main` is `IO ()` -- it can do something (some actions) with `IO` and nothing `()` is returned. You may wonder why it is not `IO Int` (with a return code). It is because giving a return code is also an IO action and you can do it from `main` with functions from `System.Exit`. + +Now, let's take a look at basic IO examples: + +```haskell +main1 :: IO () +main1 = putStr "Hello, Haskeller!" -- putStr :: String -> IO () + +main2 :: IO () +main2 = putStrLn "Hello, Haskeller!" -- putStrLn :: String -> IO () + +main3 :: IO () +main3 = do + putStr "Haskell " + putChar 'F' -- putChar :: Char -> IO () + putChar 'T' + putChar 'W' + putStrLn "! Don't you think?!" + +-- pure function +sayHello :: String -> String +sayHello name = "Hello, " ++ name ++ "!" + +main4 :: IO () +main4 = do + putStrLn "Enter your name:" + name <- getLine -- getLine :: IO String, see getChar & getContents + putStrLn . sayHello $ name + +-- custom IO action +promptInt :: IO Int +promptInt = do + putStr "Enter single integer: " + inpt <- getLine -- unwraps from IO (inpt :: String) + return (read inpt) -- return wraps with IO, read :: String -> Int + +compute x y = 50 * x + y + +main5 :: IO () +main5 = do + intA <- promptInt + intB <- promptInt + putStrLn ("Result: ++ show . compute $ intA intB) + +main6 :: IO () +main6 = print 1254 -- print = putStrLn . show +``` + +### What does `do` do? + +It doesn't look so weird if you recall how imperative programming works... But we are in the functional world now, so what is going on? Haskell provides [do notation](https://en.wikibooks.org/wiki/Haskell/do_notation), which is just a syntactic sugar for chaining actions and bindings (not just IO, in general!) in a simple manner instead of using `>>` (*then*) and `>>=` (*bind*) operators of the typeclass `Monad`. We cover this topic in detail in the next lecture, right now you can remember that although `do` looks imperative, it is actually still pure thanks to a smart "trick". + +When you use the binding operator `<-`, it means that the result of a bound action can be used in following actions. In the example with `main4`, IO action `getLine` is of type `IO String` and you want to use the wrapped `String` - you *bind* the result to name `name` and then use it in combination with pure function `sayHello` for the following action that will do the output. The `do` block consists of actions and bindings and binding cannot be the last one! + +You might have noticed the `return` in custom `promptInt` action. This is a confusing thing for beginners, as `return` here has **nothing to do** with imperative languages return. The confusing thing is that it *looks* very much like it. However, conceptually it is not a control-flow expression, but just a function of the typeclass `Monad` which is used for wrapping back something, in this case `return :: String -> IO String`. This is one of the reasons why PureScript got rid of `return` and uses `pure` instead. Again, we will look at this in detail in the next lecture. + +### Be `interact`ive + +A very interesting construct for building a simple CLI is `interact :: (String -> String) -> IO ()`. The interact function takes a function of type `String -> String` as its argument. The **entire** input from the standard input device is passed to this function as its argument, and the resulting string is output on the standard output device. Btw. this is a nice example of a higher-order function at work, right? + +```haskell +import Data.Char + +main1 :: IO () +main1 = interact (map toUpper) + +main2 :: IO () +main2 = interact (show . length) + +main3 :: IO () +main3 = interact reverse +``` + +As is emphasized, it works with an entire input. If you've tried the examples above, you could observe a difference made by lazy evaluation in the first case. If you need to interact by lines or by words, you can create helper functions for that easily. + +```haskell +eachLine :: (String -> String) -> (String -> String) +eachLine f = unlines . f . lines + +eachWord :: (String -> String) -> (String -> String) +eachWord f = unwords . f . words + +main5 :: IO () +main5 = interact (eachLine reverse) + +main6 :: IO () +main6 = interact (eachWord reverse) + +chatBot "Hello" = "Hi, how are you?" +chatBot "Fine" = "Lucky you... bye!" +chatBot "Bad" = "Me too!" +chatBot _ = "Sorry, I'm too dumb to understand this..." + +main7 :: IO () +main7 = interact (eachLine chatBot) +``` + +### IO with files + +Working with files is very similar to working with console IO. As you may already know, most of IO for consoles is built by using IO for files with system "file" stdin and stdout. Such thing is called a `Handle` in Haskell and it is well described in [System.IO](http://hackage.haskell.org/package/base/docs/System-IO.html#t:Handle). + +```haskell +main1 :: IO () +main1 = withFile "test.txt" ReadMode $ \handle -> do + fileSize <- hFileSize handle + print fileSize + xs <- getlines handle + sequence_ $ map (putStrLn . reverse) xs + +main2 :: IO () +main2 = do + handle <- openFile "test.txt" ReadMode -- :: IO Handle + fileSize <- hFileSize handle + print fileSize + hClose handle +``` + +In a similar manner, you can work with binary files (you would use `ByteString`s) and temporary files. To work with sockets (network communication), you can use a library like [network](hackage.haskell.org/package/network/) or specifically for HTTP [wreq](https://hackage.haskell.org/package/wreq) and [req](https://hackage.haskell.org/package/req). + +For some well-known file formats there are libraries ready, so you don't have to work with them over and over again just with functions from `Prelude`: + +* JSON: [aeson](https://hackage.haskell.org/package/aeson) +* YAML: [yaml](https://hackage.haskell.org/package/yaml) +* XML: [xml](https://hackage.haskell.org/package/xml), [hxt](https://hackage.haskell.org/package/hxt), or [xeno](https://hackage.haskell.org/package/xeno) +* CSV: [cassava](https://hackage.haskell.org/package/cassava) or [csv](https://hackage.haskell.org/package/csv/docs/Text-CSV.html) +* INI: [ini](https://hackage.haskell.org/package/ini) + +... and so on. Also, you probably know the fabulous [pandoc](https://pandoc.org), which is written in Haskell -- and you can use it as a [library](https://hackage.haskell.org/package/pandoc)! + +Hmmm, who said that Haskell is just for math and mad academics? ;-) + +### Arguments and env variables + +Another way of interacting with a program is via its command-line arguments and environment variables. Again, there is a little bit clumsy but simple way in [System.Environment](https://hackage.haskell.org/package/base/docs/System-Environment.html) and then some fancy libraries that can help you with more complex cases... + +```haskell +main :: IO () +main = do + progName <- getProgName -- IO String + print progName + path <- getExecutablePath -- IO String + print path + args <- getArgs -- :: IO [String] + print args + user <- lookupEnv "USER" -- :: IO (Maybe String), vs. getEnv :: IO String + print user + env <- getEnvironment -- :: IO [(String, String)] + print env +``` + +The most used library for [command line option parser](https://wiki.haskell.org/Command_line_option_parsers) is [cmdargs](http://hackage.haskell.org/package/cmdargs): + +```haskell +{-# LANGUAGE DeriveDataTypeable #-} +module Sample where +import System.Console.CmdArgs + +data Sample = Hello {whom :: String} + | Goodbye + deriving (Show, Data, Typeable) + +hello = Hello{whom = def} +goodbye = Goodbye + +main = do + args <- cmdArgs (modes [hello, goodbye]) + print args +``` + +For a more complex example, visit their documentation -- for example, `hlint` or `diffy` use this one. + +## Handling errors + +As we saw, a very elegant way way how to handle errors is using `Maybe` or `Either` types. This is a preferred way with obvious advantages, however, in practice, it may still come to a more explosive situation. + +### error + +`error` is a special function which stops execution with given message: + +``` +Prelude> error "Stop now" +*** Exception: Stop now +CallStack (from HasCallStack): + error, called at :1:1 in interactive:Ghci1 +``` + +There is another quite similar one - `errorWithoutStackTrace`: + +``` +Prelude> errorWithoutStackTrace "Stop now without stack trace" +*** Exception: Stop now without stack trace +``` + +It is obviously even worse than just `error` because you somewhere deep in your code say something about rendering the error... + +### undefined + +Special case of error is that something is `undefined` and it does not accept any message: + +``` +Prelude> undefined +*** Exception: Prelude.undefined +CallStack (from HasCallStack): + error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err + undefined, called at :5:1 in interactive:Ghci1 +``` + +Semantically, it can be used where the value is not defined (for example when you want to divide by zero). Sometimes you can see it used as a basic placeholder with meaning "Not implemented yet". For such things, you can use custom `error` or some specialized package like [Development.Placeholders](hackage.haskell.org/package/placeholders/docs/Development-Placeholders.html), which are more suitable. + +### throw, try and catch + +We have `throw`, `try` and `catch`, but those are functions - not keywords! + +``` +Prelude> import Control.Exception +Prelude Control.Exception> :type try +try :: Exception e => IO a -> IO (Either e a) +Prelude Control.Exception> :type throw +throw :: Exception e => e -> a +Prelude Control.Exception> :type catch +catch :: Exception e => IO a -> (e -> IO a) -> IO a +``` + +If you are interested you can read the documentation of [Control.Exception](https://hackage.haskell.org/package/base/docs/Control-Exception.html), however, exceptions are considered an anti-pattern in Haskell and you should always try to deal with potential errors in a more systematic way using types. We will slightly get back to these after getting the notion of Monads. + +```haskell +import System.IO +import Control.Exception + +myHandler exc = do + putStrLn "Oops, error occured while trying to read the file" + putStrLn $ "It failed with: " ++ show (exc :: SomeException) + +main = handle myHandler $ do + fp <- openFile "test.txt" ReadMode + fileSize <- hFileSize fp + print fileSize + hClose fp +``` + +## Advanced Typeclasses + +### Foldable Recall the time when we were talking about folds... The `Foldable` type class provides a generalization of list folding (`foldr` and friends) and operations derived from it to arbitrary data structures. The class does not require the Functor superclass in order to allow containers like Set or StorableVector that have additional constraints on the element type. But many interesting Foldables are also Functors. A foldable container is a container with the added property that its items can be 'folded' to a summary value. Recall what `foldr` and `foldl` do... @@ -33,7 +334,7 @@ class Foldable (t :: * -> *) where This class is very straight-forward, it has no specific laws, but it is very powerful as we've already known... It allows you to create new or use various containers with same generic functions like `null`, `length`, `elem`, `minimum`, `maximum`, and others seamlessly and without any problems. For more, see [Data.Foldable](https://hackage.haskell.org/package/base/docs/Data-Foldable.html). -### Specialized folds +#### Specialized folds Aside functions defined in `Foldable` typeclass, there are some additional specialized folds that can be very useful and avoid reinventing the wheel in your code: @@ -54,7 +355,7 @@ notElem :: (Foldable t, Eq a) => a -> t a -> Bool find :: Foldable t => (a -> Bool) -> t a -> Maybe a ``` -### Foldable and Applicative +#### Foldable and Applicative Then, there are some specialized functions that are useful when you have `Applicative` objects in a `Foldable` structure or want to apply them over a `Foldable` structure. *(Notice the underscores)* @@ -101,7 +402,7 @@ Prelude Data.Foldable> asum [Nothing, Just "b"] Just "b" ``` -### Foldable and Monad +#### Foldable and Monad Similarly, there are also same folds for `Monad`s, just naming is a bit different: @@ -115,7 +416,7 @@ sequence_ :: (Foldable t, Monad m) => t (m a) -> m () msum :: (Foldable t, MonadPlus m) => t (m a) -> m a -- An alternative is described in this tutorial ``` -## Traversable +### Traversable A `Traversable` type is a kind of upgraded `Foldable` with use of `Functor`. Where Foldable gives you the ability to go through the structure processing the elements (*catamorphism*) but throwing away the "shape", `Traversable` allows you to do that whilst preserving the "shape" and, e.g., putting new values in. Traversable is what we need for `mapM` and `sequence`: note the apparently surprising fact that the versions ending with an underscore (e.g., `mapM_`) are in a different typeclass - in `Foldable`. @@ -130,7 +431,7 @@ class (Functor t, Foldable t) => Traversable (t :: * -> *) where `Traversable` has, unlike `Foldable`, a few laws (naturality, identity, composition, ...). For more, see [Data.Traversable](https://hackage.haskell.org/package/base/docs/Data-Traversable.html). -### No more underscore +#### No more underscore Indeed, some functions from `Foldable` are in `Traversable` without trailing `_` and it means "preserving the structure": @@ -163,7 +464,7 @@ ciao ["ahoj","hello","ciao"] ``` -## State +### State As you know, Haskell is great, there is no mutability, which results in reference transparency, everything has a mathematical foundations, and life is perfect. Or not? Using a mutable state is clearly over-used in imperative and object-oriented programming, at the same time, the concept of "state" may be inherently present in the modelled domain and so we need to deal with it. @@ -190,7 +491,7 @@ instance Monad (State s) where There are two interesting things. First, `State` is a record type with one field of type `s -> (a, s)`. Then `(>>=)` operator returns a `State` with a function that first runs `p` on given state `s0`, get intermediary result `(x, s)` and returns the result of running `k x` on `s1` -### Example: simple counter +#### Example: simple counter Let's look at a simple example: @@ -215,7 +516,7 @@ main = do If still not clear, try to read about `Reader` and `Writer` monads and look [here](http://adit.io/posts/2013-06-10-three-useful-monads.html) or into the classic [LYAH](http://learnyouahaskell.com/for-a-few-monads-more#state). -### Random in Haskell +#### Random in Haskell When you are using `System.Random`, you work with `State`: `State` is the generator for pseudorandom numbers (some equation with "memory"). @@ -228,7 +529,7 @@ main = do print $ take 10 ns ``` -### Parser +#### Parser Another typical example where you use `State` is when you want to parse something. So for this purpose, we have Parser monadic combinator as follows: @@ -238,7 +539,35 @@ newtype Parser a = Parser (parse :: String -> [(a,String)]) A very nice example is [here](http://dev.stephendiehl.com/fun/002_parsers.html). -## Alternative and MonadPlus +For custom `Read` instance, you can do something simple, but it still works as a parser and uses `ReadS` (S ~ state): + +```haskell +import Data.Char + +data Time = Time Int Int Int + +timePart x + | x < 10 = '0' : show x + | otherwise = show x + +instance Show Time where + show (Time hours minutes seconds) = timePart hours ++ ":" ++ timePart minutes ++ ":" ++ timePart seconds + +instance Read Time where + readsPrec _ (h1:h2:':':m1:m2:':':s1:s2:remaining) + | all isDigit [h1,h2,m1,m2,s1,s2] = [(Time h m s, remaining)] + | otherwise = [] + where + h = mkTimePart h1 h2 + m = mkTimePart m1 m2 + s = mkTimePart s1 s2 + mkTimePart x1 x2 = 10 * digitToInt x1 + digitToInt x2 + readsPrec _ _ = [] +``` + +Notice that you have to return list of tuples of type `(a, String)` just like in `parse`. The `readsPrec` gets and `Int` and then `String` where the number serves for the operator precedence of the enclosing context (can be often omitted). + +### Alternative and MonadPlus We are used use type `Maybe` when the result can be something or fail/nothing, and lists when there are many results of the same type and arbitrary size. Typeclasses `Alternative` and `MonadPlus` are here to provide a generic way of aggregating results together. `Maybe` and `[]` are its instances - read more: [Control.Applicative#Alternative](https://hackage.haskell.org/package/base/docs/Control-Applicative.html#t:Alternative) and [Control.Monad#MonadPlus](https://hackage.haskell.org/package/base/docs/Control-Monad.html#t:MonadPlus). You might find this very useful for [parsing](https://en.wikibooks.org/wiki/Haskell/Alternative_and_MonadPlus#Example:_parallel_parsing). @@ -264,7 +593,7 @@ a "a" ``` -### `guard` (don't mix with guards!) +#### `guard` (don't mix with guards!) An interesting function related to `Alternative` is `guard :: Alternative f => Bool -> f ()`. What does it do? It works like a guard in a sequence of actions! @@ -283,13 +612,13 @@ main = do print "OK, it is bigger than 100" ``` -## Monad Transformers +### Monad Transformers We have seen how monads can help handling IO actions, Maybe, lists, and state. With monads providing a common way to use such useful general-purpose tools, a natural thing we might want to do is using the capabilities of several monads at once. For instance, a function could use both I/O and Maybe exception handling. While a type like `IO (Maybe a)` would work just fine, it would force us to do pattern matching within `IO` do-blocks to extract values, something that the `Maybe` monad was meant to spare us from. Sounds like a dead end, right?! Luckily, we have monad transformers that can be used to combine monads in this way, save us time, and make the code easier to read. -### MaybeT +#### MaybeT Consider following simple program: @@ -332,7 +661,7 @@ askPassphrase = do lift $ putStrLn "Insert your new passphrase:" For more about monad transformers visit [this](https://en.wikibooks.org/wiki/Haskell/Monad_transformers) and the [transformers](https://hackage.haskell.org/package/transformers) package. There is also a very nice chapter about them in http://haskellbook.com. -## Category and Arrow +### Category and Arrow Recall what was told about Category Theory in the last tutorial. In Haskell, we have also typeclasses `Category` and `Arrow` (Do you remember? Alias for *morphisms*.). We mention it here just as an interesting part of Haskell and let you explore it if you are interested... @@ -409,7 +738,7 @@ arrow3 :: Arrow a => a [a1] [a1] A good explanation with nice visualization is in the chapter [Understanding Arrows](https://en.wikibooks.org/wiki/Haskell/Understanding_arrows) at wikibooks. -## Lens (and the taste of Template Haskell) +### Lens (and the taste of Template Haskell) The last thing we are going to get into this time is *Lens*. It is something that can make you a way more productive while working with records and especially nested records - which is something really common in non-trivial programs. @@ -423,7 +752,7 @@ The combinators in [Control.Lens](https://hackage.haskell.org/package/lens) prov If you are interested in `Control.Lens`, follow links in *Further reading* sections... -### Lens example +#### Lens example First, let's try example without *lens*: @@ -481,7 +810,7 @@ Line {_pA = Point2D {_x = 0, _y = 2}, _pB = Point2D {_x = 5, _y = 7}} Line {_pA = Point2D {_x = 0, _y = 0}, _pB = Point2D {_x = 10, _y = 7}} ``` -### What is `makeLenses` +#### What is `makeLenses` The function `makeLenses` indeed does some magic! From its type signature `makeLenses :: Language.Haskell.TH.Syntax.Name -> Language.Haskell.TH.Lib.DecsQ`, you can see it has something to do with [Template Haskell](https://wiki.haskell.org/Template_Haskell). It is GHC extension that allows metaprogramming. In this case, the function `makeLenses` builds lenses (and traversals) with a sensible default configuration. You need to provide the data type name where the record starts with an underscore and it will basically generate lenses for you. @@ -489,10 +818,12 @@ Template Haskell is very powerful and allows you to do interesting stuff, but it ## Task assignment -The homework to practice typeclasses from this tutorial is in repository [MI-AFP/hw08](https://github.com/MI-AFP/hw08). +The homework to practice typeclasses from this tutorial is in repository [MI-AFP/hw06](https://github.com/MI-AFP/hw06). ## Further reading +* [Haskell - Introduction to IO](https://wiki.haskell.org/Introduction_to_IO) +* [Haskell - Handling errors in Haskell](https://wiki.haskell.org/Handling_errors_in_Haskell) * [Haskell - Foldable](https://en.wikibooks.org/wiki/Haskell/Foldable) * [Haskell - Traversable](https://en.wikibooks.org/wiki/Haskell/Traversable) * [Haskell - State monad](https://en.wikibooks.org/wiki/Haskell/Understanding_monads/State) @@ -509,4 +840,3 @@ The homework to practice typeclasses from this tutorial is in repository [MI-AFP * [SchoolOfHaskell - Lens tutorial](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/a-little-lens-starter-tutorial) * [Lenses In Pictures](http://adit.io/posts/2013-07-22-lenses-in-pictures.html) * [Next Level MTL - George Wilson - BFPG 2016-06 (Lens, Monad transformers)](https://www.youtube.com/watch?v=GZPup5Iuaqw) - diff --git a/tutorials/06_io-test-doc.md b/tutorials/06_io-test-doc.md deleted file mode 100644 index 476b3b2..0000000 --- a/tutorials/06_io-test-doc.md +++ /dev/null @@ -1,594 +0,0 @@ -# Basic IO, tests, and documentation - -So far, we were working with pure functions (without side effects). You should be able to build complex libraries, use standard containers and other data types, write clean Haskell code, and understand most of the basic programs written by Haskellers. This time we will take a look at basics of working with a user (or other) input/output, writing tests and documentation. - -## Basic IO - -When you need to incorporate input and output (CLI, files, sockets, etc.), you bring impureness into your program. Obviously, IO brings side effects (it interacts with the environment and changes the global state). It can be a bit complicated and so we won't go deep into theory this time and instead, we will just show how to use it. Theoretical part will be covered in the future. - -### The main and gets + puts - -If you know C/C++, Python, or other programming languages, you should be familiar with "main". As in other languages, `main` is defined to be the entry point of a Haskell program. For Stack projects, it is located in a file inside `app` directory and can be defined in `package.yaml` in `executables` section (it is possible to have multiple entrypoints per program). The type of `main` is `IO ()` -- it can do something (some actions) with `IO` and nothing `()` is returned. You may wonder why it is not `IO Int` (with a return code). It is because giving a return code is also an IO action and you can do it from `main` with functions from `System.Exit`. - -Now, let's take a look at basic IO examples: - -```haskell -main1 :: IO () -main1 = putStr "Hello, Haskeller!" -- putStr :: String -> IO () - -main2 :: IO () -main2 = putStrLn "Hello, Haskeller!" -- putStrLn :: String -> IO () - -main3 :: IO () -main3 = do - putStr "Haskell " - putChar 'F' -- putChar :: Char -> IO () - putChar 'T' - putChar 'W' - putStrLn "! Don't you think?!" - --- pure function -sayHello :: String -> String -sayHello name = "Hello, " ++ name ++ "!" - -main4 :: IO () -main4 = do - putStrLn "Enter your name:" - name <- getLine -- getLine :: IO String, see getChar & getContents - putStrLn . sayHello $ name - --- custom IO action -promptInt :: IO Int -promptInt = do - putStr "Enter single integer: " - inpt <- getLine -- unwraps from IO (inpt :: String) - return (read inpt) -- return wraps with IO, read :: String -> Int - -compute x y = 50 * x + y - -main5 :: IO () -main5 = do - intA <- promptInt - intB <- promptInt - putStrLn ("Result: ++ show . compute $ intA intB) - -main6 :: IO () -main6 = print 1254 -- print = putStrLn . show -``` - -### What does `do` do? - -It doesn't look so weird if you recall how imperative programming works... But we are in the functional world now, so what is going on? Haskell provides [do notation](https://en.wikibooks.org/wiki/Haskell/do_notation), which is just a syntactic sugar for chaining actions and bindings (not just IO, in general!) in a simple manner instead of using `>>` (*then*) and `>>=` (*bind*) operators of the typeclass `Monad`. We cover this topic in detail in the next lecture, right now you can remember that although `do` looks imperative, it is actually still pure thanks to a smart "trick". - -When you use the binding operator `<-`, it means that the result of a bound action can be used in following actions. In the example with `main4`, IO action `getLine` is of type `IO String` and you want to use the wrapped `String` - you *bind* the result to name `name` and then use it in combination with pure function `sayHello` for the following action that will do the output. The `do` block consists of actions and bindings and binding cannot be the last one! - -You might have noticed the `return` in custom `promptInt` action. This is a confusing thing for beginners, as `return` here has **nothing to do** with imperative languages return. The confusing thing is that it *looks* very much like it. However, conceptually it is not a control-flow expression, but just a function of the typeclass `Monad` which is used for wrapping back something, in this case `return :: String -> IO String`. This is one of the reasons why PureScript got rid of `return` and uses `pure` instead. Again, we will look at this in detail in the next lecture. - -### Be `interact`ive - -A very interesting construct for building a simple CLI is `interact :: (String -> String) -> IO ()`. The interact function takes a function of type `String -> String` as its argument. The **entire** input from the standard input device is passed to this function as its argument, and the resulting string is output on the standard output device. Btw. this is a nice example of a higher-order function at work, right? - -```haskell -import Data.Char - -main1 :: IO () -main1 = interact (map toUpper) - -main2 :: IO () -main2 = interact (show . length) - -main3 :: IO () -main3 = interact reverse -``` - -As is emphasized, it works with an entire input. If you've tried the examples above, you could observe a difference made by lazy evaluation in the first case. If you need to interact by lines or by words, you can create helper functions for that easily. - -```haskell -eachLine :: (String -> String) -> (String -> String) -eachLine f = unlines . f . lines - -eachWord :: (String -> String) -> (String -> String) -eachWord f = unwords . f . words - -main5 :: IO () -main5 = interact (eachLine reverse) - -main6 :: IO () -main6 = interact (eachWord reverse) - -chatBot "Hello" = "Hi, how are you?" -chatBot "Fine" = "Lucky you... bye!" -chatBot "Bad" = "Me too!" -chatBot _ = "Sorry, I'm too dumb to understand this..." - -main7 :: IO () -main7 = interact (eachLine chatBot) -``` - -### IO with files - -Working with files is very similar to working with console IO. As you may already know, most of IO for consoles is built by using IO for files with system "file" stdin and stdout. Such thing is called a `Handle` in Haskell and it is well described in [System.IO](http://hackage.haskell.org/package/base/docs/System-IO.html#t:Handle). - -```haskell -main1 :: IO () -main1 = withFile "test.txt" ReadMode $ \handle -> do - fileSize <- hFileSize handle - print fileSize - xs <- getlines handle - sequence_ $ map (putStrLn . reverse) xs - -main2 :: IO () -main2 = do - handle <- openFile "test.txt" ReadMode -- :: IO Handle - fileSize <- hFileSize handle - print fileSize - hClose handle -``` - -In a similar manner, you can work with binary files (you would use `ByteString`s) and temporary files. To work with sockets (network communication), you can use a library like [network](hackage.haskell.org/package/network/) or specifically for HTTP [wreq](https://hackage.haskell.org/package/wreq) and [req](https://hackage.haskell.org/package/req). - -For some well-known file formats there are libraries ready, so you don't have to work with them over and over again just with functions from `Prelude`: - -* JSON: [aeson](https://hackage.haskell.org/package/aeson) -* YAML: [yaml](https://hackage.haskell.org/package/yaml) -* XML: [xml](https://hackage.haskell.org/package/xml), [hxt](https://hackage.haskell.org/package/hxt), or [xeno](https://hackage.haskell.org/package/xeno) -* CSV: [cassava](https://hackage.haskell.org/package/cassava) or [csv](https://hackage.haskell.org/package/csv/docs/Text-CSV.html) -* INI: [ini](https://hackage.haskell.org/package/ini) - -... and so on. Also, you probably know the fabulous [pandoc](https://pandoc.org), which is written in Haskell -- and you can use it as a [library](https://hackage.haskell.org/package/pandoc)! - -Hmmm, who said that Haskell is just for math and mad academics? ;-) - -### Arguments and env variables - -Another way of interacting with a program is via its command-line arguments and environment variables. Again, there is a little bit clumsy but simple way in [System.Environment](https://hackage.haskell.org/package/base/docs/System-Environment.html) and then some fancy libraries that can help you with more complex cases... - -```haskell -main :: IO () -main = do - progName <- getProgName -- IO String - print progName - path <- getExecutablePath -- IO String - print path - args <- getArgs -- :: IO [String] - print args - user <- lookupEnv "USER" -- :: IO (Maybe String), vs. getEnv :: IO String - print user - env <- getEnvironment -- :: IO [(String, String)] - print env -``` - -The most used library for [command line option parser](https://wiki.haskell.org/Command_line_option_parsers) is [cmdargs](http://hackage.haskell.org/package/cmdargs): - -```haskell -{-# LANGUAGE DeriveDataTypeable #-} -module Sample where -import System.Console.CmdArgs - -data Sample = Hello {whom :: String} - | Goodbye - deriving (Show, Data, Typeable) - -hello = Hello{whom = def} -goodbye = Goodbye - -main = do - args <- cmdArgs (modes [hello, goodbye]) - print args -``` - -For a more complex example, visit their documentation -- for example, `hlint` or `diffy` use this one. - -## Testing - -Haskellers sometimes say that "When the programme compiles, it is correct!" There is a lot of truth to it, as you may have already experienced: the strong static type system does not allow you to make many errors, especially the most common (and insidious) "stupid" ones. At the same time, this saying is obviously exaggerated and there is still quite some space for a programme to be buggy. This is why traditional unit testing has its place in Haskell. Moreover, Haskell also offers an even more powerful types of testing such as property testing and mutation testing. - -### HUnit - -[HUnit](https://hackage.haskell.org/package/HUnit) is a unit testing framework for Haskell, inspired by the JUnit tool for Java and similar ones. For developers familiar with unit testing, this framework is very simple to use. First, you define several test cases that you put in a test list (instead of test class as in Java). A single test case is composed optionally of some data preparation and assertions. The result of running tests consists of four numbers: cases, tried, errors and failures. - -```haskell -import Test.HUnit - -test1 = TestCase (assertEqual "for (foo 3)," (1,2) (foo 3)) -test2 = TestCase (do (x,y) <- partA 3 - assertEqual "for the first result of partA," 5 x - b <- partB y - assertBool ("(partB " ++ show y ++ ") failed") b) - -tests = TestList [ TestLabel "test1" test1 - , TestLabel "test2" test2 - ] -``` - -It is very simple, there are set of assertions (with many operator aliases) and three variants of tests that can be composed: `TestCase`, `TestLabel`, and `TestList`. It is sufficient for a simple unit testing, but tests are not that nicely readable as with `hspec` (see below). - -``` -GHCi> runTestTT tests -Cases: 2 Tried: 2 Errors: 0 Failures: 0 -``` - -### QuickCheck - -A different approach to testing is provided by [QuickCheck](https://hackage.haskell.org/package/QuickCheck). It is a library for random testing of program properties. You can specify some "laws" in your application and this library will check with a given number of randomly (but smartly) generated instances if there is not some counterexample violating the laws. Such laws or specifications are expressed in Haskell, using combinators defined in the QuickCheck library. QuickCheck provides combinators to define properties, to observe the distribution of test data, and to define test data generators. All from a simple example to complex tutorials of such definitions are explained in the [manual](http://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html). - -#### Basic properties - -With QuickCheck you can, for example, check if some function is associative or commutative: - -```haskell -import Test.QuickCheck - -prop_ReverseReverse :: String -> Bool -prop_ReverseReverse xs = reverse (reverse xs) == xs - -prop_AddAssociative :: Int -> Int -> Int -> Bool -prop_AddAssociative x y z = (x + y) + z == x + (y + z) - -prop_AddCommutative :: Int -> Int -> Bool -prop_AddCommutative x y = x + y == y + x - -main = do - quickCheck prop_ReverseReverse - quickCheck prop_AddAssociative - quickCheck prop_AddCommutative - quickCheck (withMaxSuccess 100000 prop_AddCommutative) -``` - -QuickCheck generates automatically randomized values (it tries to start with corner cases) and it tries to find a counterexample. There is some default behaviour that you can override, for example, to request more random values. - -``` -% runhaskell qctests.hs -+++ OK, passed 100 tests. -+++ OK, passed 100 tests. -+++ OK, passed 100 tests. -+++ OK, passed 100000 tests. -``` - -#### Own datatypes and `Arbitrary` - -If you have your own types, you need to make them an instance of the typeclass `Arbitrary`. Then QuickCheck can generate examples for them, too: - -```haskell -import Test.QuickCheck -import Control.Monad - -data Person = Person - { name :: String - , age :: Int - } deriving (Show, Eq) - -mature :: Person -> Person -mature p = p { age = 18 } - -instance Arbitrary Person where - arbitrary = do - nameLength <- choose (1, 30) - randomName <- replicateM nameLength (elements ['a'..'z']) - randomAge <- choose (0, 100) - return Person { name = randomName - , age = randomAge - } - -prop_Mature :: Person -> Bool -prop_Mature p - | age p < 18 = mature p == p { age = 18 } - | otherwise = mature p == p - -main :: IO () -main = quickCheck prop_Mature -``` - -### Hspec - -[Hspec](https://hackage.haskell.org/package/hspec) is a testing framework for Haskell. It is inspired by the Ruby library RSpec. Some of Hspec's distinctive features are: - -* a friendly DSL for defining tests, -* integration with QuickCheck, SmallCheck, and HUnit, -* parallel test execution, -* automatic discovery of test files. - -Tests written in Hspec are very readable, intuitive and powerful. It allows integration with HUnit as well as with QuickCheck so it is sort of an "ultimate testing framework for Haskell". - -```haskell -import Test.Hspec -import Test.QuickCheck -import Control.Exception (evaluate) - -main :: IO () -main = hspec $ do - describe "Prelude.head" $ do - it "returns the first element of a list" $ do - head [23 ..] `shouldBe` (23 :: Int) - - it "returns the first element of an *arbitrary* list" $ - property $ \x xs -> head (x:xs) == (x :: Int) - - it "throws an exception if used with an empty list" $ do - evaluate (head []) `shouldThrow` anyException -``` - -#### Expectations - -There are many predefined expectation functions that are typically written in infix notation to improve readability of specs. They are in separated packages and projects: https://github.com/hspec/hspec-expectations#readme - -* `shouldBe` = equality test -* `shouldNotBe` = inequality test -* `shouldSatisfy` = test if result satisfies given property as function `:: a -> Bool` -* `shouldStartWith` = test if list has a given prefix -* `shouldEndWith` = test if list has a given suffix -* `shouldContain` = test if list contains sublist -* `shouldMatchList` = test if lists have same elements (can be different order) -* `shouldReturn` = test for actions (where is `return` used) -* `shouldThrow` = test if throwing some exception or error (`evaluate` from `Control.Exception` must be used, then there are predefined expectations: `anyException`, `anyErrorCall`, `errorCall "message"`, `anyIOException`, and `anyArithException`. - -#### Property check - -The integration of hspec with QuickCheck is really great, all you need to do is to write a `property`. - -```haskell -import Test.Hspec -import Test.Hspec.QuickCheck (modifyMaxSuccess) -import Test.QuickCheck - -prop_ReverseReverse :: String -> Bool -prop_ReverseReverse xs = reverse (reverse xs) == xs - -prop_AddAssociative :: Int -> Int -> Int -> Bool -prop_AddAssociative x y z = (x + y) + z == x + (y + z) - -prop_AddCommutative :: Int -> Int -> Bool -prop_AddCommutative x y = x + y == y + x - -main :: IO () -main = hspec $ do - describe "addition" $ do - context "when used with ints" $ do - modifyMaxSuccess (const 20000) $ do - it "is associative" $ - property prop_AddAssociative - it "is commutative" $ - property prop_AddCommutative - describe "reverse" $ do - context "when used with string" $ do - it "negates when used twice" $ - property prop_ReverseReverse -``` - -``` -% runhaskell Spec.hs - -addition - when used with ints - is associative - is commutative -reverse - when used with string - negates when used twice - -Finished in 0.2299 seconds -3 examples, 0 failures -``` - -#### Complex test suites - -It is a good practice to separate specs according to your modules including the directories. - -```haskell -import Test.Hspec - -import qualified FooSpec -import qualified Foo.BarSpec -import qualified BazSpec - -main :: IO () -main = hspec spec - -spec :: Spec -spec = do - describe "Foo" FooSpec.spec - describe "Foo.Bar" Foo.BarSpec.spec - describe "Baz" BazSpec.spec -``` - -This may become quite elaborate for big projects, so [hspec-discover](https://hackage.haskell.org/package/hspec-discover) provides an automatic spec discovery. All you need to do in the main spec file is this: - -```haskell --- file test/Spec.hs -{-# OPTIONS_GHC -F -pgmF hspec-discover #-} -``` - -Another useful thing for bigger projects is [Test.Hspec.Formatters](https://hackage.haskell.org/package/hspec/docs/Test-Hspec-Formatters.html) module. In the following example, `main` is in a different module than `spec` created by automatic discovery: - -```haskell -import Test.Hspec -import Test.Hspec.Runner -import Test.Hspec.Formatters -import Test.Hspec.QuickCheck (modifyMaxSuccess) -import Test.QuickCheck - -prop_ReverseReverse :: String -> Bool -prop_ReverseReverse xs = reverse (reverse xs) == xs - -prop_AddAssociative :: Int -> Int -> Int -> Bool -prop_AddAssociative x y z = (x + y) + z == x + (y + z) - -prop_AddCommutative :: Int -> Int -> Bool -prop_AddCommutative x y = x + y == y + x - -main :: IO () -main = hspecWith defaultConfig {configFormatter = Just progress} $ do - describe "addition" $ do - context "when used with ints" $ do - modifyMaxSuccess (const 20000) $ do - it "is associative" $ - property prop_AddAssociative - it "is commutative" $ - property prop_AddCommutative - describe "reverse" $ do - context "when used with string" $ do - it "negates when used twice" $ - property prop_ReverseReverse -``` - -``` -% runhaskell playground.hs -... -Finished in 0.2056 seconds -3 examples, 0 failures -``` - -### MuCheck - -Mutation Testing is a special type of software testing where certain statements in a source code are mutated (changed by mutation operators) and then we check if the test cases recognize the errors. In Haskell, there is [MuCheck](https://hackage.haskell.org/package/MuCheck). - -```haskell --- https://github.com/vrthra/mucheck -import Test.MuCheck.TestAdapter.AssertCheck - -qsort :: [Int] -> [Int] -qsort [] = [] -qsort (x:xs) = qsort l ++ [x] ++ qsort r - where l = filter (< x) xs - r = filter (>= x) xs - -{-# ANN sortEmpty "Test" #-} -sortEmpty = assertCheck $ qsort [] == [] - -{-# ANN sortSorted "Test" #-} -sortSorted = assertCheck $ qsort [1,2,3,4] == [1,2,3,4] - -{-# ANN sortRev "Test" #-} -sortRev = assertCheck $ qsort [4,3,2,1] == [1,2,3,4] - -{-# ANN sortSame "Test" #-} -sortSame = assertCheck $ qsort [1,1,1,1] == [1,1,1,1] - -{-# ANN sortNeg "Test" #-} -sortNeg = assertCheck $ qsort [-1,-2,3] == [-2,-1,3] - -main = do - assertCheckResult sortEmpty - assertCheckResult sortSorted - assertCheckResult sortRev - assertCheckResult sortSame - assertCheckResult sortNeg -``` - -``` -% cabal run sample-test -% cabal run mucheck -- -tix sample-test.tix Examples/AssertCheckTest.hs -Total mutants: 19 (basis for %) - Covered: 13 - Sampled: 13 - Errors: 0 (0%) - Alive: 1/19 - Killed: 12/19 (63%) -``` - -Sadly this interesting project with a [paper](https://www.researchgate.net/publication/266659188_MuCheck_An_extensible_tool_for_mutation_testing_of_haskell_programs) published is dead for some years. Hopefully, someone will take over its maintenance or at least fork it and contribute to it (:wink: term project). It also has some interesting integrations like [MuCheck-QuickCheck](https://hackage.haskell.org/package/MuCheck-QuickCheck), [MuCheck-HUnit](https://hackage.haskell.org/package/MuCheck-HUnit), or [MuCheck-Hspec](https://hackage.haskell.org/package/MuCheck-Hspec). - -## Haddock (documentation) - -Haskell projects, like any other projects, should have good documentation of source code. In Haskell, the tool for documentation is called [Haddock](https://www.haskell.org/haddock/) and works similarly to JavaDoc or JSDoc or other XYDoc by annotating the code using comments with special meaning: - -```haskell -{-| -Module : W -Description : Short description -Copyright : (c) Some Guy, 2013 - Someone Else, 2014 -License : GPL-3 -Maintainer : sample@email.com -Stability : experimental -Portability : POSIX - -Here is a longer description of this module, containing some -commentary with @some markup@. --} -module W where - --- | The 'square' function squares an integer. --- It takes one argument, of type 'Int'. -square :: Int -> Int -square x = x * x - -class C a where - -- | This is the documentation for the 'f' method - f :: a -> Int - -- | This is the documentation for the 'g' method - g :: Int -> a - -data R a b = - R { -- | This is the documentation for the 'a' field - a :: a, - -- | This is the documentation for the 'b' field - b :: b - } - -data S a b = - S { a :: a -- ^ This is the documentation for the 'a' field - , b :: b -- ^ This is the documentation for the 'b' field - } -``` - -For more information about using Haddock and writing the documentation of source code in Haskell check http://haskell-haddock.readthedocs.io/en/latest/index.html or https://www.haskell.org/haddock/doc/html/ (the examples above are from this documentation). - -For building the documentation within a *stack project*, you can use `stack haddock` command, which generates `index.html` file. - -## Publish your project - -If you think that other people might be interested in your project and want to use it standalone or as part of their project (as a dependency), you can publish your project on GitHub and also on Hackage: - -* [GitHub - create a repo](https://help.github.com/articles/create-a-repo/) -* [Hackage - upload](https://hackage.haskell.org/upload) - -Your project should be: -* tested (write tests for your project so you can prove that it is working properly), -* documented (try to describe everything in your code to "strangers" with low Haskell knowledge), -* licensed (pick a suitable license - https://choosealicense.com can help you). - -Another advantage of publishing is that your project can get attention and community can help you improve it -- they create issues, forks and pull requests. - -## Using CI (Travis CI) - -When you are developing a project and sharing it with a community, you want to show that it is working well and you also want to check if contributions to your code are not breaking it. For that, you can use CI tools (continuous integration) which allows you to run tests (or other scripts) automatically. There are many CI tools these days: Travis CI, Jenkins, Circle CI, Appveyor, Semaphore, GitLab CI, etc. - -All (well, almost all) CIs need some specification what they should do with your project. If you are using GitHub, then Travis CI is one of the good choices for you. Just create `.travis.yml` in your repository and register project in Travis CI. - -```yaml -# https://docs.haskellstack.org/en/stable/travis_ci/ -sudo: false -# Not Haskell with cabal but with stack tool -language: c -cache: - directories: - - ~/.stack -addons: - apt: - packages: - - libgmp-dev -before_install: - - mkdir -p ~/.local/bin - - export PATH=$HOME/.local/bin:$PATH - - travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' -install: - - stack --no-terminal --install-ghc test --only-dependencies -script: - - stack --no-terminal test --haddock --no-haddock-deps -``` - -For Haskell, you can use `.travis.yml` above or read the [documentation](https://docs.travis-ci.com/user/languages/haskell/). - -## Task assignment - -The homework to practice IO basics, testing, and writing project documentation is in repository [MI-AFP/hw06](https://github.com/MI-AFP/hw06). - -## Further reading - -* [A Gentle Introduction to Haskell - Input/Output](https://www.haskell.org/tutorial/io.html) -* [Haskell - Simple input and output](https://en.wikibooks.org/wiki/Haskell/Simple_input_and_output) -* [Real World Haskell - Testing and quality assurance](http://book.realworldhaskell.org/read/testing-and-quality-assurance.html) -* [WikiBooks - Haskell: Testing](https://en.wikibooks.org/wiki/Haskell/Testing) -* [Haddock User Guide](https://www.haskell.org/haddock/doc/html/index.html) -* [QuickCheck and Magic of Testing](https://www.fpcomplete.com/blog/2017/01/quickcheck) diff --git a/tutorials/11_performance-debug.md b/tutorials/07_test-doc-debug.md similarity index 51% rename from tutorials/11_performance-debug.md rename to tutorials/07_test-doc-debug.md index 264bad0..6f04c30 100644 --- a/tutorials/11_performance-debug.md +++ b/tutorials/07_test-doc-debug.md @@ -1,6 +1,409 @@ -# Performance and Debugging +# Tests, Documentation, Debugging and Performance -During this tutorial, we will take a look how to improve the performance of a Haskell program and how to debug it. We will use very simple example - [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number). +## Testing + +Haskellers sometimes say that "When the programme compiles, it is correct!" There is a lot of truth to it, as you may have already experienced: the strong static type system does not allow you to make many errors, especially the most common (and insidious) "stupid" ones. At the same time, this saying is obviously exaggerated and there is still quite some space for a programme to be buggy. This is why traditional unit testing has its place in Haskell. Moreover, Haskell also offers an even more powerful types of testing such as property testing and mutation testing. + +### HUnit + +[HUnit](https://hackage.haskell.org/package/HUnit) is a unit testing framework for Haskell, inspired by the JUnit tool for Java and similar ones. For developers familiar with unit testing, this framework is very simple to use. First, you define several test cases that you put in a test list (instead of test class as in Java). A single test case is composed optionally of some data preparation and assertions. The result of running tests consists of four numbers: cases, tried, errors and failures. + +```haskell +import Test.HUnit + +test1 = TestCase (assertEqual "for (foo 3)," (1,2) (foo 3)) +test2 = TestCase (do (x,y) <- partA 3 + assertEqual "for the first result of partA," 5 x + b <- partB y + assertBool ("(partB " ++ show y ++ ") failed") b) + +tests = TestList [ TestLabel "test1" test1 + , TestLabel "test2" test2 + ] +``` + +It is very simple, there are set of assertions (with many operator aliases) and three variants of tests that can be composed: `TestCase`, `TestLabel`, and `TestList`. It is sufficient for a simple unit testing, but tests are not that nicely readable as with `hspec` (see below). + +``` +GHCi> runTestTT tests +Cases: 2 Tried: 2 Errors: 0 Failures: 0 +``` + +### QuickCheck + +A different approach to testing is provided by [QuickCheck](https://hackage.haskell.org/package/QuickCheck). It is a library for random testing of program properties. You can specify some "laws" in your application and this library will check with a given number of randomly (but smartly) generated instances if there is not some counterexample violating the laws. Such laws or specifications are expressed in Haskell, using combinators defined in the QuickCheck library. QuickCheck provides combinators to define properties, to observe the distribution of test data, and to define test data generators. All from a simple example to complex tutorials of such definitions are explained in the [manual](http://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html). + +#### Basic properties + +With QuickCheck you can, for example, check if some function is associative or commutative: + +```haskell +import Test.QuickCheck + +prop_ReverseReverse :: String -> Bool +prop_ReverseReverse xs = reverse (reverse xs) == xs + +prop_AddAssociative :: Int -> Int -> Int -> Bool +prop_AddAssociative x y z = (x + y) + z == x + (y + z) + +prop_AddCommutative :: Int -> Int -> Bool +prop_AddCommutative x y = x + y == y + x + +main = do + quickCheck prop_ReverseReverse + quickCheck prop_AddAssociative + quickCheck prop_AddCommutative + quickCheck (withMaxSuccess 100000 prop_AddCommutative) +``` + +QuickCheck generates automatically randomized values (it tries to start with corner cases) and it tries to find a counterexample. There is some default behaviour that you can override, for example, to request more random values. + +``` +% runhaskell qctests.hs ++++ OK, passed 100 tests. ++++ OK, passed 100 tests. ++++ OK, passed 100 tests. ++++ OK, passed 100000 tests. +``` + +#### Own datatypes and `Arbitrary` + +If you have your own types, you need to make them an instance of the typeclass `Arbitrary`. Then QuickCheck can generate examples for them, too: + +```haskell +import Test.QuickCheck +import Control.Monad + +data Person = Person + { name :: String + , age :: Int + } deriving (Show, Eq) + +mature :: Person -> Person +mature p = p { age = 18 } + +instance Arbitrary Person where + arbitrary = do + nameLength <- choose (1, 30) + randomName <- replicateM nameLength (elements ['a'..'z']) + randomAge <- choose (0, 100) + return Person { name = randomName + , age = randomAge + } + +prop_Mature :: Person -> Bool +prop_Mature p + | age p < 18 = mature p == p { age = 18 } + | otherwise = mature p == p + +main :: IO () +main = quickCheck prop_Mature +``` + +### Hspec + +[Hspec](https://hackage.haskell.org/package/hspec) is a testing framework for Haskell. It is inspired by the Ruby library RSpec. Some of Hspec's distinctive features are: + +* a friendly DSL for defining tests, +* integration with QuickCheck, SmallCheck, and HUnit, +* parallel test execution, +* automatic discovery of test files. + +Tests written in Hspec are very readable, intuitive and powerful. It allows integration with HUnit as well as with QuickCheck so it is sort of an "ultimate testing framework for Haskell". + +```haskell +import Test.Hspec +import Test.QuickCheck +import Control.Exception (evaluate) + +main :: IO () +main = hspec $ do + describe "Prelude.head" $ do + it "returns the first element of a list" $ do + head [23 ..] `shouldBe` (23 :: Int) + + it "returns the first element of an *arbitrary* list" $ + property $ \x xs -> head (x:xs) == (x :: Int) + + it "throws an exception if used with an empty list" $ do + evaluate (head []) `shouldThrow` anyException +``` + +#### Expectations + +There are many predefined expectation functions that are typically written in infix notation to improve readability of specs. They are in separated packages and projects: https://github.com/hspec/hspec-expectations#readme + +* `shouldBe` = equality test +* `shouldNotBe` = inequality test +* `shouldSatisfy` = test if result satisfies given property as function `:: a -> Bool` +* `shouldStartWith` = test if list has a given prefix +* `shouldEndWith` = test if list has a given suffix +* `shouldContain` = test if list contains sublist +* `shouldMatchList` = test if lists have same elements (can be different order) +* `shouldReturn` = test for actions (where is `return` used) +* `shouldThrow` = test if throwing some exception or error (`evaluate` from `Control.Exception` must be used, then there are predefined expectations: `anyException`, `anyErrorCall`, `errorCall "message"`, `anyIOException`, and `anyArithException`. + +#### Property check + +The integration of hspec with QuickCheck is really great, all you need to do is to write a `property`. + +```haskell +import Test.Hspec +import Test.Hspec.QuickCheck (modifyMaxSuccess) +import Test.QuickCheck + +prop_ReverseReverse :: String -> Bool +prop_ReverseReverse xs = reverse (reverse xs) == xs + +prop_AddAssociative :: Int -> Int -> Int -> Bool +prop_AddAssociative x y z = (x + y) + z == x + (y + z) + +prop_AddCommutative :: Int -> Int -> Bool +prop_AddCommutative x y = x + y == y + x + +main :: IO () +main = hspec $ do + describe "addition" $ do + context "when used with ints" $ do + modifyMaxSuccess (const 20000) $ do + it "is associative" $ + property prop_AddAssociative + it "is commutative" $ + property prop_AddCommutative + describe "reverse" $ do + context "when used with string" $ do + it "negates when used twice" $ + property prop_ReverseReverse +``` + +``` +% runhaskell Spec.hs + +addition + when used with ints + is associative + is commutative +reverse + when used with string + negates when used twice + +Finished in 0.2299 seconds +3 examples, 0 failures +``` + +#### Complex test suites + +It is a good practice to separate specs according to your modules including the directories. + +```haskell +import Test.Hspec + +import qualified FooSpec +import qualified Foo.BarSpec +import qualified BazSpec + +main :: IO () +main = hspec spec + +spec :: Spec +spec = do + describe "Foo" FooSpec.spec + describe "Foo.Bar" Foo.BarSpec.spec + describe "Baz" BazSpec.spec +``` + +This may become quite elaborate for big projects, so [hspec-discover](https://hackage.haskell.org/package/hspec-discover) provides an automatic spec discovery. All you need to do in the main spec file is this: + +```haskell +-- file test/Spec.hs +{-# OPTIONS_GHC -F -pgmF hspec-discover #-} +``` + +Another useful thing for bigger projects is [Test.Hspec.Formatters](https://hackage.haskell.org/package/hspec/docs/Test-Hspec-Formatters.html) module. In the following example, `main` is in a different module than `spec` created by automatic discovery: + +```haskell +import Test.Hspec +import Test.Hspec.Runner +import Test.Hspec.Formatters +import Test.Hspec.QuickCheck (modifyMaxSuccess) +import Test.QuickCheck + +prop_ReverseReverse :: String -> Bool +prop_ReverseReverse xs = reverse (reverse xs) == xs + +prop_AddAssociative :: Int -> Int -> Int -> Bool +prop_AddAssociative x y z = (x + y) + z == x + (y + z) + +prop_AddCommutative :: Int -> Int -> Bool +prop_AddCommutative x y = x + y == y + x + +main :: IO () +main = hspecWith defaultConfig {configFormatter = Just progress} $ do + describe "addition" $ do + context "when used with ints" $ do + modifyMaxSuccess (const 20000) $ do + it "is associative" $ + property prop_AddAssociative + it "is commutative" $ + property prop_AddCommutative + describe "reverse" $ do + context "when used with string" $ do + it "negates when used twice" $ + property prop_ReverseReverse +``` + +``` +% runhaskell playground.hs +... +Finished in 0.2056 seconds +3 examples, 0 failures +``` + +### MuCheck + +Mutation Testing is a special type of software testing where certain statements in a source code are mutated (changed by mutation operators) and then we check if the test cases recognize the errors. In Haskell, there is [MuCheck](https://hackage.haskell.org/package/MuCheck). + +```haskell +-- https://github.com/vrthra/mucheck +import Test.MuCheck.TestAdapter.AssertCheck + +qsort :: [Int] -> [Int] +qsort [] = [] +qsort (x:xs) = qsort l ++ [x] ++ qsort r + where l = filter (< x) xs + r = filter (>= x) xs + +{-# ANN sortEmpty "Test" #-} +sortEmpty = assertCheck $ qsort [] == [] + +{-# ANN sortSorted "Test" #-} +sortSorted = assertCheck $ qsort [1,2,3,4] == [1,2,3,4] + +{-# ANN sortRev "Test" #-} +sortRev = assertCheck $ qsort [4,3,2,1] == [1,2,3,4] + +{-# ANN sortSame "Test" #-} +sortSame = assertCheck $ qsort [1,1,1,1] == [1,1,1,1] + +{-# ANN sortNeg "Test" #-} +sortNeg = assertCheck $ qsort [-1,-2,3] == [-2,-1,3] + +main = do + assertCheckResult sortEmpty + assertCheckResult sortSorted + assertCheckResult sortRev + assertCheckResult sortSame + assertCheckResult sortNeg +``` + +``` +% cabal run sample-test +% cabal run mucheck -- -tix sample-test.tix Examples/AssertCheckTest.hs +Total mutants: 19 (basis for %) + Covered: 13 + Sampled: 13 + Errors: 0 (0%) + Alive: 1/19 + Killed: 12/19 (63%) +``` + +Sadly this interesting project with a [paper](https://www.researchgate.net/publication/266659188_MuCheck_An_extensible_tool_for_mutation_testing_of_haskell_programs) published is dead for some years. Hopefully, someone will take over its maintenance or at least fork it and contribute to it (:wink: term project). It also has some interesting integrations like [MuCheck-QuickCheck](https://hackage.haskell.org/package/MuCheck-QuickCheck), [MuCheck-HUnit](https://hackage.haskell.org/package/MuCheck-HUnit), or [MuCheck-Hspec](https://hackage.haskell.org/package/MuCheck-Hspec). + +## Haddock (documentation) + +Haskell projects, like any other projects, should have good documentation of source code. In Haskell, the tool for documentation is called [Haddock](https://www.haskell.org/haddock/) and works similarly to JavaDoc or JSDoc or other XYDoc by annotating the code using comments with special meaning: + +```haskell +{-| +Module : W +Description : Short description +Copyright : (c) Some Guy, 2013 + Someone Else, 2014 +License : GPL-3 +Maintainer : sample@email.com +Stability : experimental +Portability : POSIX + +Here is a longer description of this module, containing some +commentary with @some markup@. +-} +module W where + +-- | The 'square' function squares an integer. +-- It takes one argument, of type 'Int'. +square :: Int -> Int +square x = x * x + +class C a where + -- | This is the documentation for the 'f' method + f :: a -> Int + -- | This is the documentation for the 'g' method + g :: Int -> a + +data R a b = + R { -- | This is the documentation for the 'a' field + a :: a, + -- | This is the documentation for the 'b' field + b :: b + } + +data S a b = + S { a :: a -- ^ This is the documentation for the 'a' field + , b :: b -- ^ This is the documentation for the 'b' field + } +``` + +For more information about using Haddock and writing the documentation of source code in Haskell check http://haskell-haddock.readthedocs.io/en/latest/index.html or https://www.haskell.org/haddock/doc/html/ (the examples above are from this documentation). + +For building the documentation within a *stack project*, you can use `stack haddock` command, which generates `index.html` file. + +### Publish your project + +If you think that other people might be interested in your project and want to use it standalone or as part of their project (as a dependency), you can publish your project on GitHub and also on Hackage: + +* [GitHub - create a repo](https://help.github.com/articles/create-a-repo/) +* [Hackage - upload](https://hackage.haskell.org/upload) + +Your project should be: +* tested (write tests for your project so you can prove that it is working properly), +* documented (try to describe everything in your code to "strangers" with low Haskell knowledge), +* licensed (pick a suitable license - https://choosealicense.com can help you). + +Another advantage of publishing is that your project can get attention and community can help you improve it -- they create issues, forks and pull requests. + +### Using CI (Travis CI) + +When you are developing a project and sharing it with a community, you want to show that it is working well and you also want to check if contributions to your code are not breaking it. For that, you can use CI tools (continuous integration) which allows you to run tests (or other scripts) automatically. There are many CI tools these days: Travis CI, Jenkins, Circle CI, Appveyor, Semaphore, GitLab CI, etc. + +All (well, almost all) CIs need some specification what they should do with your project. If you are using GitHub, then Travis CI is one of the good choices for you. Just create `.travis.yml` in your repository and register project in Travis CI. + +```yaml +# https://docs.haskellstack.org/en/stable/travis_ci/ +sudo: false +# Not Haskell with cabal but with stack tool +language: c +cache: + directories: + - ~/.stack +addons: + apt: + packages: + - libgmp-dev +before_install: + - mkdir -p ~/.local/bin + - export PATH=$HOME/.local/bin:$PATH + - travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' +install: + - stack --no-terminal --install-ghc test --only-dependencies +script: + - stack --no-terminal test --haddock --no-haddock-deps +``` + +For Haskell, you can use `.travis.yml` above or read the [documentation](https://docs.travis-ci.com/user/languages/haskell/). + +## Performance and Debugging + +During this tutorial, we will also take a look how to improve the performance of a Haskell program and how to debug it. We will use very simple example - [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number). ```haskell import System.Environment @@ -17,11 +420,11 @@ main = do print . fibonacci . read . head $ args ``` -## Measuring time and memory +### Measuring time and memory When you want to check the performance of a program and compare two programs or algorithms in terms of time or memory consumption, you need to measure it. -### Basic `time` +#### Basic `time` The `time` command is one of the well-known Linux commands for programmers. It can be used to show how long a command takes to run. That makes it Very useful if you are a developer and you want to test the performance of your program or script. Especially to compare the time of programs written in other languages "from outside". For basic usage, you will get three numbers: @@ -31,7 +434,7 @@ The `time` command is one of the well-known Linux commands for programmers. It c Then `user`+`sys` gives information how much actual CPU time your process used - in total on all cores. This number can be then higher than `real` if your program uses multiple threads. -``` +```console % /usr/bin/time -p runhaskell FibonacciNaive.hs 25 75025 real 0.33 @@ -49,7 +452,7 @@ Exit Status: 0 Page Faults: 0 ``` -### Benchmarking with Criterion +#### Benchmarking with Criterion If you are interested in such optimizations and improving your application or comparing various algorithms or their implementations, then you might find interesting to use a benchmarking library. In Haskell is the most used one called [Criterion](http://www.serpentine.com/criterion/). It provides a powerful but simple way to measure software performance. It provides both a framework for executing and analyzing benchmarks and a set of driver functions that makes it easy to build and run benchmarks and to analyze their results. @@ -75,7 +478,7 @@ main = defaultMain [ It has very nice outputs with a form of interactive HTML pages with charts and comparisons and has many options to use. -``` +```console % runhaskell FibonacciNaiveCriterion.hs benchmarking fib/ 5 time 7.319 μs (6.980 μs .. 7.821 μs) @@ -100,7 +503,7 @@ variance introduced by outliers: 11% (moderately inflated) runhaskell FibonacciNaiveCriterion.hs 15.98s user 0.04s system 99% cpu 16.055 total ``` -### Measure allocations with Weigh +#### Measure allocations with Weigh The package [weigh](https://hackage.haskell.org/package/weigh) provides a simple interface to measure the memory usage of a Haskell value or function. @@ -122,7 +525,7 @@ main = mainWith $ do It provides a nice output as plain text table, but it is also possible to change the format to markdown. -``` +```console % ./FibonacciNaiveWeigh Case Allocated GCs @@ -131,11 +534,11 @@ fib 10 24,304 0 fib 25 33,509,936 63 ``` -## Performance +### Performance Now we are able to measure something and compare algorithms, but how to improve the numbers we get if we really need it? -### Basic ideas +#### Basic ideas When you are not satisfied with the performance of your application, then before any sophisticated optimization steps by using strictness, unboxed types, calling FFI, etc., you should consider if you prefer faster application over better readability. Then another important thing to think about is design if it is not slow by using "naive" algorithm, using an inappropriate data structure (List instead of Set or Map), etc. @@ -159,7 +562,7 @@ main = do Just a very simple re-thinking can have some impact: -``` +```console % /usr/bin/time -p runhaskell FibonacciBetter.hs 25 75025 real 0.24 @@ -167,7 +570,7 @@ user 0.22 sys 0.02 ``` -``` +```console % runhaskell FibonacciBetterCriterion.hs benchmarking fib/ 5 time 3.412 μs (3.235 μs .. 3.591 μs) @@ -193,7 +596,7 @@ variance introduced by outliers: 91% (severely inflated) runhaskell FibonacciBetterCriterion.hs 15.90s user 0.07s system 100% cpu 15.954 total ``` -``` +```console % ./FibonacciBetterWeigh Case Allocated GCs @@ -202,7 +605,7 @@ fib 10 1,712 0 fib 25 37,000 0 ``` -### Boxed vs. Unboxed types +#### Boxed vs. Unboxed types Now, we are going to briefly mention is the difference between boxed and unboxed types. Although it is a low-level concern and with regular Haskell programming, you can avoid these terms, it is good to know what is it about when you see it in other's code or in a documentation. @@ -230,7 +633,7 @@ main :: IO () main = print (I# (fibonacci 25#)) ``` -``` +```console % /usr/bin/time -p runhaskell FibonacciUnboxed.hs 75025 real 0.30 @@ -240,7 +643,7 @@ sys 0.03 For more information, visit [GHC.Exts]() and [GHC.Prim](). -### Strictness with types +#### Strictness with types In the previous lessons, we touched the topic of enforcing strictness with `!` in patterns ([bang patterns](https://ocharles.org.uk/blog/posts/2014-12-05-bang-patterns.html)) and in function application with `$!` operator. Similarly, we can use `!` with type fields like this: @@ -265,7 +668,7 @@ data T2 = T2 Double {-# UNPACK #-} !Int -- => T2 Double Int# We mention this just because of differences in performance of types we are going to describe now. You don't need to use strict or unboxed types within your work if you don't need to have time/space optimizations and if yes, consider reading [Haskell High Performance Programming](https://github.com/TechBookHunter/Free-Haskell-Books/blob/master/book/Haskell%20High%20Performance%20Programming.pdf). -### GHC optimization flags +#### GHC optimization flags If you know optimization with GCC, then you won't be surprised how it works with GHC: @@ -275,7 +678,7 @@ If you know optimization with GCC, then you won't be surprised how it works with Then there are also `-f*` platform-independent flags, that allows you to turn on and off individual optimizations. For more information, please visit [GHC documentation](http://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-optimisation.html). -### Concurrency and Parallelism +#### Concurrency and Parallelism Haskell (of course) supports parallelism or concurrency in order to achieve faster and efficient computation. For parallelism and concurrency visit [wiki.haskell.org/Parallel](https://wiki.haskell.org/Parallel). You can both: @@ -300,7 +703,7 @@ main = do x <- parfib 30; print x GHC supports running programs in parallel on an SMP (symmetric multiprocessor) or multi-core machine. Just compile your program using the `-threaded` switch and then run it with RTS option `-N ` (where `` is the number of simultaneous threads). See [GHC docs](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/parallel.html) for more information. -### FFI +#### FFI As with many other programming languages, Haskell supports [FFI (Foreign Function Interface)](https://wiki.haskell.org/Foreign_Function_Interface) that allows co-operating with programs written with other languages. We've already could see that in the example of Haste in DS Wizard where there were some JS bindings. But you can also use it to call some functions from C++ or Rust: @@ -333,7 +736,7 @@ main = do print . cfib . read . head $ args ``` -``` +```console % gcc -c -o fib.o fib.cpp % ghc --make -o ffi_fib FibonacciFFI.hs fib.o Linking ffi_fib ... @@ -346,11 +749,11 @@ sys 0.00 Similarly, there is `foreign export` to expose some Haskell functions to other FFIs. Nice example is here: [jarrett/cpphs](https://github.com/jarrett/cpphs). -## Debugging +### Debugging Even if you are a good Haskell programmer, things can go wrong and especially in big projects it is a nontrivial challenge to find out where you did some mistake. Going thru the code in multiple functions, inner functions, various modules, etc. can be painful. Luckilly, there are some ways how to debug Haskell program and some are pretty easy and similar to well-known. -### Tracing with `Debug.Trace` +#### Tracing with `Debug.Trace` You should already know how to use GHC and GHCi to compile, link and examine Haskell programs. The simplest tool to use for debugging is the `trace` from [Debug.Trace](https://hackage.haskell.org/package/base.0/docs/Debug-Trace.html) which outputs the trace message given as its first argument, before returning the second argument as its result. There are many more *traces* defined for different cases: `traceShow`, `traceId`, `traceStack`, `traceIO`, `traceM`, etc. So you can use it for custom debugging output anywhere in the code. @@ -387,7 +790,7 @@ main = do print (fib2 4) ``` -``` +```console % runhaskell FibonacciTrace.hs fib1 4 fib1 2 @@ -408,7 +811,7 @@ fib2 0 3 ``` -### GHCi debugger +#### GHCi debugger If you need a better debugger, you can use [GHCi debugger](https://downloads.haskell.org/~ghc/7.4.1/docs/html/users_guide/ghci-debugger.html) (other compilers, such as Hugs, have some different), which allows: @@ -439,7 +842,7 @@ n :: Integer = 3 *Main> ``` -### `debug` package +#### `debug` package An interesting solution brings also the [debug](https://hackage.haskell.org/package/debug) package (and related extensions). It uses *Template Haskell* to examine the code and algorithms. @@ -458,11 +861,20 @@ debug [d| |] ``` +## Task assignment + +The homework to practice IO (again), testing, and writing project documentation is in repository [MI-AFP/hw07](https://github.com/MI-AFP/hw07). + ## Further reading +* [A Gentle Introduction to Haskell - Input/Output](https://www.haskell.org/tutorial/io.html) +* [Haskell - Simple input and output](https://en.wikibooks.org/wiki/Haskell/Simple_input_and_output) +* [Real World Haskell - Testing and quality assurance](http://book.realworldhaskell.org/read/testing-and-quality-assurance.html) +* [WikiBooks - Haskell: Testing](https://en.wikibooks.org/wiki/Haskell/Testing) +* [Haddock User Guide](https://www.haskell.org/haddock/doc/html/index.html) +* [QuickCheck and Magic of Testing](https://www.fpcomplete.com/blog/2017/01/quickcheck) * [Haskell - Debugging](https://wiki.haskell.org/Debugging) * [Haskell - Performance](https://wiki.haskell.org/Performance) * [Haskell - Concurrency](https://wiki.haskell.org/Concurrency) * [Real World Haskell - Concurrent and Multicore Programming](http://book.realworldhaskell.org/read/concurrent-and-multicore-programming.html) * [GHC - Concurrent and Parallel Haskell](https://downloads.haskell.org/~ghc/7.0.3/docs/html/users_guide/lang-parallel.html) - diff --git a/tutorials/09_webapp.md b/tutorials/08_webapp.md similarity index 85% rename from tutorials/09_webapp.md rename to tutorials/08_webapp.md index 6e30cd7..7d44dc3 100644 --- a/tutorials/09_webapp.md +++ b/tutorials/08_webapp.md @@ -236,6 +236,10 @@ main = scotty 3000 $ do Surprisingly easy, right?! +### Templating + +Writing HTML fragments as strings and compose them together can be fairly bothersome. For that purpose, there are libraries for templating known from other languages and frameworks as well. + #### Blaze templates One of the well-known and widely used solution for HTML templates is [Blaze HTML](https://hackage.haskell.org/package/blaze-html). It is "a blazingly fast HTML combinator library for the Haskell programming language". A huge advantage of Blaze is that you write HTML via HTML-like lightweight DSL in Haskell with the great type system. Blaze and Haskell won't allow you to make a non-sense HTML, although it does not check a full conformity, of course. @@ -293,7 +297,92 @@ context "name" = MuVariable "Haskell" A useful source of information what can you do with Hastache are [examples](https://github.com/lymar/hastache/tree/master/examples). -#### Databases +#### Ginger + +If you are familiar with Python and more specifically with Jinja2 templates, then you might be a fan of [Ginger](https://ginger.tobiasdammers.nl/guide/) library. It is implementation of Jinja2 templates in Haskell and it have quite nice documentation. Usage is quite simple, just look at the example: + +You have some HTML file with Jinja2 tags: + +```html +{# There could be some "extends" for layout #} + + + + {{ title }} + + +

Hello, {{ name }}!

+ + {% if condition %} +

Condition is true

+ {% else %} +

Condition is false or undefined

+ {% endif %} + + +``` + +Then you can load template, supply context into it, and render as text: + +```haskell +{-# LANGUAGE OverloadedStrings #-} +module Main where + +import Data.HashMap.Strict (fromList, HashMap) +import qualified Data.HashMap.Strict as HashMap +import Data.Hashable +import Data.Text (Text) +import System.Exit (exitFailure) +import System.IO (IOMode(ReadMode), openFile, hGetContents) +import System.IO.Error (tryIOError) +import Text.Ginger + (makeContextHtml, Template, toGVal, runGinger, parseGingerFile, VarName) +import Text.Ginger.GVal (ToGVal, GVal) +import Text.Ginger.Html (htmlSource) + + +-- A simple hashmap that we'll use as our template context +sampleContext :: HashMap Text Text +sampleContext = fromList [("name", "Alice")] + + +-- Given a Template and a HashMap of context, render the template to Text +render :: Template -> HashMap VarName Text -> Text +render template contextMap = + let contextLookup = flip scopeLookup contextMap + context = makeContextHtml contextLookup + in htmlSource $ runGinger context template + + +-- Wrapper around HashMap.lookup that applies toGVal to the value found. +-- Any value referenced in a template, returned from within a template, or used +-- in a template context, will be a GVal +scopeLookup + :: (Hashable k, Eq k, ToGVal m b) + => k -> HashMap.HashMap k b -> GVal m +scopeLookup key context = toGVal $ HashMap.lookup key context + + +loadFileMay :: FilePath -> IO (Maybe String) +loadFileMay fn = + tryIOError (loadFile fn) >>= \e -> + case e of + Right contents -> return (Just contents) + Left _ -> return Nothing + + where + loadFile :: FilePath -> IO String + loadFile fn' = openFile fn' ReadMode >>= hGetContents + +main :: IO () +main = do + template <- parseGingerFile loadFileMay "base.html" + case template of + Left err -> print err >> exitFailure + Right template' -> print $ render template' sampleContext +``` + +### Databases Again, several abstraction levels are available. First, you can employ a low-level approach where you incorporate SQL in the code. For that, you can usually use module `Database.X` where `X` is type of datase: @@ -476,24 +565,24 @@ This is just a simple (but often sufficient) example. Of course, you can test mu Here are a few examples of simple and more complex web apps: * [dbushenko/scotty-blog](https://github.com/dbushenko/scotty-blog) -* [DataStewardshipWizard/ds-wizard](https://github.com/DataStewardshipWizard/ds-wizard/tree/master/DSServer) -* [DataStewardshipWizard/dsw-server](https://github.com/DataStewardshipWizard/dsw-server) +* [ds-wizard/legacy-wizard](https://github.com/ds-wizard/legacy-wizard) +* [ds-wizard/dsw-server](https://github.com/ds-wizard/dsw-server) ## Continuation-style web development -We would also like to raise your attention to an interesting approach to web development based on the [continuation](https://wiki.haskell.org/Continuation). A continuation is "something" that enables you to save the state of computation, suspend it (do something else) and later resume it. This "something" may be a first-class language feature (such as in Scheme), or a library feature -- in Haskell, surprisingly, we have a continuation monad ;-). +We would also like to raise your attention to an interesting approach to web development based on the [continuation](https://wiki.haskell.org/Continuation). A continuation is "something" that enables you to save the state of computation, suspend it (do something else) and later resume it. This "something" may be a first-class language feature (such as in Scheme), or a library feature - in Haskell, surprisingly, we have a continuation monad ;-). -A need for continuation occurs typically in web development (and generally in UI development) when you want a modal dialogue. Today, most of the dialogues are handled on client-side, however if you need to do a modal dialogue on server-side, it is hard -- HTTP behaves like a programming language, which does not have subroutines, only GOTOs (URLSs are the 'line numbers'). Continuation can provide the missing abstraction here, which is embodied in the [MFlow](http://mflowdemo.herokuapp.com) library. Sadly, the project seems abandoned for several years. +A need for continuation occurs typically in web development (and generally in UI development) when you want a modal dialogue. Today, most of the dialogues are handled on client-side, however if you need to do a modal dialogue on server-side, it is hard - HTTP behaves like a programming language, which does not have subroutines, only GOTOs (URLSs are the 'line numbers'). Continuation can provide the missing abstraction here, which is embodied in the [MFlow](http://mflowdemo.herokuapp.com) library. Sadly, the project seems abandoned for several years. -At the same time, the continuation-style web server programming is typically the first choice in the Smalltalk (OOP) world -- [Seaside](http://seaside.st/) is purely continuation-based, and as such it gives a "desktop programming" experience for the web development resulting in no need of dealing with routing and URLs. As for the FP world, continuation-style web programming is surprisingly not used much in practice, but there are solutions such as the [Racket web server](https://docs.racket-lang.org/web-server/index.html) or [cl-weblocks](https://www.cliki.net/cl-weblocks) in Common Lisp. +At the same time, the continuation-style web server programming is typically the first choice in the Smalltalk (OOP) world - [Seaside](http://seaside.st/) is purely continuation-based, and as such it gives a "desktop programming" experience for the web development resulting in no need of dealing with routing and URLs. As for the FP world, continuation-style web programming is surprisingly not used much in practice, but there are solutions such as the [Racket web server](https://docs.racket-lang.org/web-server/index.html) or [cl-weblocks](https://www.cliki.net/cl-weblocks) in Common Lisp. The next time, we will deal a bit with frontend technologies for Haskell, functional reactive programming and [The JavaScript problem](https://wiki.haskell.org/The_JavaScript_Problem). So you will also see how to develop server-side and client-side separately and connect them through a (REST) API. ## Task assignment -The homework to complete a simple web app is in repository [MI-AFP/hw09](https://github.com/MI-AFP/hw09). +The homework to complete a simple web app is in repository [MI-AFP/hw08](https://github.com/MI-AFP/hw08). ## Further reading diff --git a/tutorials/09_elm-intro.md b/tutorials/09_elm-intro.md new file mode 100644 index 0000000..0108720 --- /dev/null +++ b/tutorials/09_elm-intro.md @@ -0,0 +1,924 @@ +# Elm - Introduction + +## Installation, Editors and Plugins + +To install Elm, follow the official [installation guide](https://guide.elm-lang.org/install.html). There is also a section about how to configure different editors to work with Elm. + +Make sure to install [elm-format](https://github.com/avh4/elm-format) to your editor as well. + +There is also an online live editor for Elm called [Ellie](https://ellie-app.com). + + +## What is Elm + +According to the official website [elm-lang.org](https://elm-lang.org): + +> A delightful language for reliable webapps. +> +> Generate JavaScript with great performance and no runtime exceptions. + +It is a reactive pure functional programming language with syntax inspired by Haskell that compiles to JavaScript. It is designed for building reliable web applications with no runtime exceptions. Elm is one of the solutions for [the JavaScript problem](https://wiki.haskell.org/The_JavaScript_Problem). The [compiler](https://github.com/elm/compiler) is implemented in Haskell. + + +Elm is language **and** a framework for building front-end web applications. + +### Advantages + +- static strong type system, no `null` or `undefined` (static code analysis, when it compiles, it works) +- pure functions (no side effects, allows tree shaking on a function level) +- everything necessary for building front end apps is already included in the language +- standard ways how to do things, so it is easy to understand other people's code +- easy refactoring + +### Disadvantages + +- need for learning a new language +- sometimes, more code is needed than it would be in JavaScript (e.g., when parsing JSONs) +- lightweight Haskell (e.g., no type classes, which can result in more boilerplate code) +- harder to find developers for Elm projects than for JavaScript projects. + +### Compared to JavaScript + +Elm has built-in common tools and features that are part of typical JavaScript stack. + +| JavaScript | Elm| +| --- | --- | +| npm/yarn | built-in | +| Webpack | built-in | +| React | built-in | +| Redux | built-in | +| TypeScript/Flow | built-in | +| Immutable.JS | built-in | + + +## Alternatives + +### [Haste](https://haste-lang.org) + +Haste is a tool for compiling Haskell code into a JavaScript code and a server-side binary. Both programs can talk to each other ensuring type-safe communication. + +### [GHCJS](https://github.com/ghcjs/ghcjs) + +GHCJS is a compiler for Haskell to JavaScript that uses GHC API. It supports a wide range of Haskell features, including all type system extensions supported by GHC. There are some interesting frameworks for building web applications based on GHCJS: + +- [Reflex](https://reflex-frp.org) - A composable, cross-platform functional reactive programming framework for Haskell. +- [miso](https://haskell-miso.org) - A tasty Haskell front-end framework. + +### [PureScript](http://www.purescript.org) + +PureScript is a strongly-typed functional programming language that compiles to JavaScript. It is similar to Haskell with [some differences](https://github.com/purescript/documentation/blob/master/language/Differences-from-Haskell.md). It has a lot of packages published in [Pursuit](https://pursuit.purescript.org). Also, some frameworks for building web applications are there, e.g.: + +- [Thermite](https://github.com/paf31/purescript-thermite) - A simple PureScript wrapper for React. +- [Halogen](https://github.com/slamdata/purescript-halogen) - A declarative, type-safe UI library for PureScript. +- [Pux](https://github.com/alexmingoia/purescript-pux) - A library for building type-safe web applications. + +If you are interested, you can have a look at this comparison: [Benchmarks: GHCJS (Reflex, Miso) & Purescript (Pux, Thermite, Halogen)](https://medium.com/@saurabhnanda/benchmarks-fp-languages-libraries-for-front-end-development-a11af0542f7e) + +### [ReasonML](https://reasonml.github.io) + +Reason is a new syntax and toolchain based on OCaml programing language created by Facebook. The syntax is closer to JavaScript than OCaml. It is intended for development of front-end web applications and compiles to JavaScript. Using existing JavaScript and OCaml packages is possible. + + +## Elm REPL + +If we have Elm installed, we can run Elm REPL by `elm repl` command. It is like `ghci` for Elm. + +``` +$ elm repl +---- Elm 0.19.0 ---------------------------------------------------------------- +Read to learn more: exit, help, imports, etc. +-------------------------------------------------------------------------------- +> +``` + + +## Elm Language + +### Basic types + +Strings are enclosed in double quotation mark `"`. We use `++` operator to join them. + +#### String + +```elm +> "Hello world!" +"Hello world!" : String + +> "Hello " ++ "world!" +"Hello world!" : String +``` + +#### Numbers + +Elm has two number types `Int` and `Float` and constrained type variable `number` which can be either `Int` or `Float`. + +```elm +> 5 + 5 +10 : number + +> 5 + 5 * 3 +20 : number + +> (5 + 5) * 3 +30 : number + +> 2 ^ 8 +256 : number + +> 2 / 3 +0.6666666666666666 : Float + +> 7 // 2 +3 : Int + +> modBy 7 2 +2 : Int +``` + +#### Bool and logical operators + +Elm has standard operators for comparison and boolean operations. Same as in Haskell, it uses `/=` operator for inequality. + +```elm +> 5 > 7 +False : Bool + +> (5 == 7) +False : Bool + +> 5 /= 7 +True : Bool + +> not (5 /= 7) +False : Bool + +> False || True +True : Bool + +> False && True +False : Bool + +> not False && True +True : Bool +``` + +### Naming things + +If we want to give a name to expression, we use the `=` operator. + + +```elm +> x = 5 +5 : number + +> x +5 : number +``` + +### Function + +The definition and function call looks the same as in Haskell. + +```elm +> linear a b x = a * x + b + : number -> number -> number -> number + +> linear 5 3 7 +38 : number +``` + +### If expression + +```elm +> if True then "It is true!" else "It is not true." +"It is true!" : String +``` + + +### Comments + +Elm has multiline and single-line comments. + +```elm +{- + a multiline comment +-} + +-- a single line comment + +``` + + +### The Elm project + +The easiest way to initialize an Elm project is to use `elm init` command. + +``` +$ elm init +Hello! Elm projects always start with an elm.json file. I can create them! + +Now you may be wondering, what will be in this file? How do I add Elm files to +my project? How do I see it in the browser? How will my code grow? Do I need +more directories? What about tests? Etc. + +Check out for all the answers! + +Knowing all that, would you like me to create an elm.json file now? [Y/n]: y +Okay, I created it. Now read that link! + +$ ls +elm.json src +``` + + +It generates `elm.json` file that defines where the source files are and what are the project dependencies. + +```json +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.0", + "dependencies": { + "direct": { + "elm/browser": "1.0.1", + "elm/core": "1.0.2", + "elm/html": "1.0.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} +``` + +### Modules + +Now, when we have our project ready, we can create some modules. A module has a name which should be the same as the file name. It has to state what expression from the module should be exposed explicitly. + + +```elm +-- src/Lib.elm + +module Lib exposing (linear) + + +linear a b x = + a * x + b +``` + +We can import the module to other modules or the repl using `import` statement. If we use just `import Lib` we need to use the full name for the expressions from the module. + +```elm +> import Lib +> Lib.linear 5 3 7 +38 : Int +``` + +We can also expose some expression and then use them without the full module name. + +```elm +> import Lib exposing (linear) +> linear 5 3 7 +38 : Int +``` + +Or we can expose everything from the module. + +```elm +> import Lib exposing (..) +> linear 5 3 7 +38 : Int +``` + +Or we can import a module with a different name. + +```elm +> import Lib as Math +> Math.linear 5 3 7 +38 : Int +``` + +### Type annotation + +Elm can infer the types based on what we are doing. However, it is a good practice to define the types. + +```elm +linear : Float -> Float -> Float -> Float +linear a b x = + a * x + b +``` + + +### Type variables + +When the specific type is not important, we can use a type variable. Type names start with an uppercase letter (e.g., `Float`), type variable can be almost any string starting with a lowercase letter. + +```elm +> List.isEmpty + : List a -> Bool + +> List.map + : (a -> b) -> List a -> List b + +``` + +#### Constrained Type Variables + +There are several constrained type variables with a special meaning defined by the Elm language. + +- `number` permits `Int` and `Float` +- `appendable` permits `String` and `List a` +- `comparable` permits `Int`, `Float`, `Char`, `String`, and lists/tuples of `comparable` values +- `compappend` permits `String` and `List comparable` + + +```elm +linear : number -> number -> number -> number +linear a b x = + a * x + b +``` + +### List + +A list is a collection of items of the same type with variable length. There is a [List](https://package.elm-lang.org/packages/elm/core/latest/List) module with various functions for working with lists. + +```elm +> numbers = [1, 3, 5, 7, 11] +[1,3,5,7,11] : List number + +> List.length numbers +5 : Int + +> List.isEmpty numbers +False : Bool + +> double n = n * 2 + : number -> number +> List.map double numbers +[2,6,10,14,22] : List number + +> List.map (\n -> n * 2) numbers +[2,6,10,14,22] : List number + + +> List.map ((*) 2) numbers +[2,6,10,14,22] : List number +``` + + +### Dict + +Dict is a mapping of unique keys to values. There is a [Dict](https://package.elm-lang.org/packages/elm-lang/core/latest/Dict) module with functions for working with dicts. + +```elm +> Dict.fromList [("Spencer", 25), ("Zoe", 21)] +Dict.fromList [("Spencer",25),("Zoe",21)] : Dict.Dict String number + +> Dict.insert "Spencer" 25 Dict.empty +Dict.fromList [("Spencer",25)] : Dict.Dict String number + +> dict = Dict.fromList [("Spencer", 25), ("Zoe", 21)] +Dict.fromList [("Spencer",25),("Zoe",21)] : Dict.Dict String number +> Dict.isEmpty dict +False : Bool +> Dict.get "Zoe" dict +Just 21 : Maybe number +> Dict.get "Frankie" dict +Nothing : Maybe number +``` + +### Tuple + +A tuple is a collection of items of various type with the fixed size. There is a [Tuple](https://package.elm-lang.org/packages/elm/core/latest/Tuple) module for working with tuples. Tuples can be used when a function returns more than one value. + + +```elm +> person = ("Joe", 21) +("Joe",21) : ( String, number ) + +> Tuple.first person +"Joe" : String + +> Tuple.second person +21 : number +``` + +We can use pattern matching for tuples in functions: + +```elm +bio : (String, Int) -> String +bio (name, age) = name ++ " is " ++ (String.fromInt age) ++ " years old." +``` + + +Elm has a limit on the maximum number of items in the tuple to be 3. If we need more, we should use a record or our own custom type. + +``` +> vector4 = (4, 10, 12, 3) +-- BAD TUPLE --------------------------------------------------------------- elm + +I only accept tuples with two or three items. This has too many: + +8| vector4 = (4, 10, 12, 3) + ^^^^^^^^^^^^^^ +I recommend switching to records. Each item will be named, and you can use the +`point.x` syntax to access them. + +Note: Read for more comprehensive advice on +working with large chunks of data in Elm. +``` + + +### Record + +Records contain keys and values. Each value can have a different type. + +```elm +> vector4 = { w = 4, x = 10, y = 12, z = 3 } +{ w = 4, x = 10, y = 12, z = 3 } + : { w : number, x : number1, y : number2, z : number3 } +``` + + +For accessing record properties, Elm has by default accessors defined as `.`. They can be used as `.`, but it is just syntactic sugar, they are just functions. + +```elm +> vector4.x +10 : number + +> .x vector4 +10 : number + +> List.map .x [vector4, vector4, vector4] +[10,10,10] : List number +``` + +If we have a look at the type of `.x` accessor, it says it is any record that has a field `x` of type `a` and returns `a`. + +```elm +> .x + : { b | x : a } -> a +``` + + +Since everything is immutable, records cannot be updated. We can create updated records though: + +```elm +> { vector4 | x = 20 } +{ w = 4, x = 20, y = 12, z = 3 } + : { w : number1, x : number, y : number2, z : number3 } +``` + + +We can use pattern matching for record keys: + +```elm +> length {w, x, y, z} = sqrt (w * w + x * x + y * y + w * w) + : { b | w : Float, x : Float, y : Float, z : a } -> Float +> length vector4 +16.61324772583615 : Float +``` + +### Type alias + +Type aliases are used to give a new name to existing types. It is useful for naming record types. + + +```elm +type alias Name = + String + + +type alias Age = + Int + + +type alias Person = + { name : Name + , age : Age + } + + +isAdult : Person -> Bool +isAdult { age } = + age >= 18 +``` + +```elm +> import Lib exposing (..) + +> joe = { name = "Joe", age = 21 } +{ age = 21, name = "Joe" } + : { age : number, name : String } + +> isAdult joe +True : Bool + +> joe = Person "Joe" 21 +{ age = 21, name = "Joe" } : Person +``` + +*Note*: Type aliases are resolved in compiled time. Therefore, they cannot be recursive. For recursion, we need to use custom types. + + +### Custom Types + +We can define custom types that have several variants. We can also associate data with a variant. + + +```elm +type Gender + = Male + | Female + + +type Tree a + = Leaf a + | Branch (Tree a) (Tree a) + + +type Profile + = Loading + | Error String + | Success Person + + + +gender = Female + +tree = Branch (Leaf 1) (Branch (Leaf 2) (Leaf 0)) + +profile = Error "Cannot load profile" + +``` + +### Pattern Matching + +```elm +isFemale : Gender -> Bool +isFemale gender = + case gender of + Male -> + False + + Female -> + True +``` + +We can use wildcard `_` for all other branches in the `case` statement or for the variables we don't need. + +```elm +isLoading : Profile -> Bool +isLoading profile = + case profile of + Loading -> + True + + _ -> + False + + +profileStatus : Profile -> String +profileStatus profile = + case profile of + Loading -> + "Loading" + + Error error -> + "Error: " ++ error + + Success _ -> + "Success!" + +``` + +We can use `::` operator for matching first element and rest of the list. + +```elm +sum : List number -> number +sum list = + case list of + head :: tail -> + head + sum tail + + [] -> + 0 +``` + + + +### Maybe + +`Maybe` is used when a result doesn't have to exist. Unlike `null` in JavaScript, we are forced to handle that case. + +```elm +type Maybe a + = Just a + | Nothing +``` + +For example empty list doesn't have a head. + +```elm +> List.head + : List a -> Maybe a +``` + +We can use the `Maybe` type in `case` statement as any other custom type. + +```elm +hello : String -> String +hello name = + "Hello, " ++ name ++ "!" + + +greet : Maybe String -> String +greet maybeName = + case maybeName of + Just name -> + hello name + + Nothing -> + "Nobody's here" +``` + +[Maybe](https://package.elm-lang.org/packages/elm/core/latest/Maybe) package contains a handful of useful functions to simplify working with maybes. + +```elm +> Maybe.withDefault + : a -> Maybe a -> a + +> Maybe.withDefault "default" (Just "value") +"value" : String + +> Maybe.withDefault "default" Nothing +"default" : String +``` + +```elm +> Maybe.map + : (a -> b) -> Maybe a -> Maybe b + +> Maybe.map ((*) 2) (Just 4) +Just 8 : Maybe number + +> Maybe.map ((*) 2) Nothing +Nothing : Maybe number +``` + +```elm +greet2 : Maybe String -> String +greet2 maybeName = + Maybe.withDefault "Nobody's here" (Maybe.map hello maybeName) +``` + + + +### Result + +`Result` is used for computations that may fail. + +```elm +type Result error value + = Ok value + | Err error +``` + +For example, we cannot calculate area with of rectangle with negative sides. + +```elm +rectArea : Float -> Float -> Result String Float +rectArea a b = + if a < 0 || b < 0 then + Err "Cannot calculate area with negative sides" + else + Ok (a * b) +``` + +There are again helpful functions in [Result](https://package.elm-lang.org/packages/elm/core/latest/Result) package. + + + +### let expressions + +It is sometimes handy to define some expression within a function to avoid repetition. We have let expressions for that. + +```elm +cubeArea : Float -> Float +cubeArea edge = + let + face = + edge ^ 2 + in + 6 * face +``` + +### Operators |>, <|, >>, << + +Elm has several operators for chaining functions and function calls together. + +#### |> + +`|>` operator takes a value and a function and applies the function to the value. + +```elm +> (|>) + : a -> (a -> b) -> b +``` + +It is useful when chaining more steps together to write readable code. + +```elm +greet3 : Maybe String -> String +greet3 maybeName = + maybeName + |> Maybe.map hello + |> Maybe.withDefault "Nobody's here" +``` + +#### <| + +`<|` operator is the opposite. It takes a function and a value and apply the function to the value. + +```elm +> (<|) + : (a -> b) -> a -> b +``` + +It is useful to avoid parentheses, the same as `$` in Haskell. + +```elm +greet4 : Maybe String -> String +greet4 maybeName = + Maybe.withDefault "Nobody's here" <| Maybe.map hello maybeName +``` + +#### >> + +`>>` is used for function composition - `(f >> g) x == g(f x)`. + + +```elm +> (>>) + : (a -> b) -> (b -> c) -> a -> c +``` + +```elm +greet5 : Maybe String -> String +greet5 = + Maybe.map hello >> Maybe.withDefault "Nobody's here" +``` + + +#### << + +`>>` is used for function composition in an opposite direction - `(f << g) x == f(g x)`. This is same as `.` in Haskell. + +```elm +> (<<) + : (b -> c) -> (a -> b) -> a -> c +``` + +```elm +greet6 : Maybe String -> String +greet6 = + Maybe.withDefault "Nobody's here" << Maybe.map hello +``` + + +### Debug + +Elm has a [Debug](https://package.elm-lang.org/packages/elm-lang/core/latest/Debug) package intended for debugging. It +should not be used in production code. + +`log` function can be used to write a value to console. + +```elm +> Debug.log "value" 1 +value: 1 +1 : number +``` + +```elm +> 5 - Debug.log "number" 4 +number: 4 +1 : number +``` + + +## Packages + +Elm packages are published on [package.elm-lang.org](https://package.elm-lang.org). There is forced [semantic versioning](https://semver.org) for Elm packages. Therefore, we don't have to worry about breaking our application while updating packages. + +In 2018, Elm 0.19 was released, and not all packages have been updated yet, so we need to check first whether the package is supported in the latest Elm version. + + + +To install a package, we use `elm install` command in the project directory. + +``` +$ elm install elm-community/maybe-extra +Here is my plan: + + Add: + elm-community/maybe-extra 5.0.0 + +Would you like me to update your elm.json accordingly? [Y/n]: y +Dependencies loaded from local cache. +Dependencies ready! +``` + +Then we can use the new package the same as we used our package before: + +```elm +> import Maybe.Extra exposing (isNothing) + +> isNothing Nothing +True : Bool + +> isNothing (Just 2) +False : Bool +``` + + + +## The Elm Architecture (TEA) + +The Elm Architecture is a pattern used by Elm applications to define the architecture. It is perfect for modularity, refactoring, code reuse and testing. It is easy to keep even the complex applications clean and maintainable with the TEA. +The Elm application has three main parts: + +- **Model** - The state of the application. +- **Update** - How to change the state. +- **View** - How to display the state. + +There are also Subscribers and Commands. We will talk about them later. + +![The Elm Architecture](./images/tea.png) + + +### Example + +Example form [Elm guide](https://guide.elm-lang.org/#a-quick-sample): + +```elm +import Browser +import Html exposing (Html, button, div, text) +import Html.Events exposing (onClick) + +main = + Browser.sandbox { init = 0, update = update, view = view } + +type Msg = Increment | Decrement + +update msg model = + case msg of + Increment -> + model + 1 + + Decrement -> + model - 1 + +view model = + div [] + [ button [ onClick Decrement ] [ text "-" ] + , div [] [ text (String.fromInt model) ] + , button [ onClick Increment ] [ text "+" ] + ] +``` + +## Running the Elm application + +### Elm Reactor + +It is a quick and simple tool to run Elm project during development. Run `elm reactor` in the project root. It starts a server at [http://localhost:8000](http://localhost:8000) where we can navigate to Elm files. + + +### Elm Make + +Tool for building Elm project. It can compile to HTML or JavaScript. For example: + +``` +$ elm make src/Main.elm --output=main.html +``` + +Generates `main.html` file with the Elm application. + + + +## Further reading + +- [An Introduction to Elm](https://guide.elm-lang.org) +- [Elm Syntax](https://elm-lang.org/docs/syntax) +- [Blazing Fast HTML](https://elm-lang.org/blog/blazing-fast-html-round-two) +- [Small Assets without the Headache](https://elm-lang.org/blog/small-assets-without-the-headache) +- [Elm in Production: Surprises & Pain Points](https://www.youtube.com/watch?v=LZj_1qVURL0) + diff --git a/tutorials/10_elm-tea.md b/tutorials/10_elm-tea.md new file mode 100644 index 0000000..00f1b0f --- /dev/null +++ b/tutorials/10_elm-tea.md @@ -0,0 +1,296 @@ +# Elm - The Elm Architecture + +## Elm program + +Elm program is set up using [Browser](https://package.elm-lang.org/packages/elm/browser/latest/Browser) module from [elm/browser](https://package.elm-lang.org/packages/elm/browser/latest/) package. There are several functions to do that based on the use case. + +- `sandbox` - Program that interacts with the user input but cannot communicate with the outside world. +- `element` - HTML element controlled by Elm that can talk to the outside world (e.g. HTTP requests). Can be embedded into a JavaScript project. +- `document` - Controls the whole HTML document, view controls `` and `<body>` elements. +- `application` - Creates single page application, Elm controls not only the whole document but also Url changes. + + +## Forms + +Form elements are created the same way as other HTML elements using functions from [Html](https://package.elm-lang.org/packages/elm/html/latest/Html) module and attributes from [Html.Attributes](https://package.elm-lang.org/packages/elm/html/latest/Html-Attributes) module from [elm/html](https://package.elm-lang.org/packages/elm/html/latest/) package. + +We can use `onInput` from [Html.Events](https://package.elm-lang.org/packages/elm/html/latest/Html-Events) module to detect input events and create a message for our update function. + +The loop is the following: +- user changes the value in an input field +- a new message is created +- the update function is called with the message and it updates the model with the new value +- input field is re-rendered with the new value + + +Here is a simple example with a single input field: + +```elm +import Browser +import Html exposing (Html, input) +import Html.Attributes exposing (placeholder, value) +import Html.Events exposing (onInput) + +main = Browser.sandbox { init = init, update = update, view = view } + +type Msg = NameChanged String + +type alias Model = { name : String } + +init : Model +init = + { name = "" } + +update : Msg -> Model -> Model +update msg model = + case msg of + NameChanged newName -> + { model | name = newName } + +view : Model -> Html Msg +view model = + input + [ placeholder "Your name" + , value model.name + , onInput NameChanged ] + [] +``` + +When we need more complex forms in our application, there are packags to handle forms like [etaque/elm-form](https://package.elm-lang.org/packages/etaque/elm-form/latest/). + + +## JSON + +It is very common to use JSON format when communicating with different APIs. In JavaScript, JSON is usually turned into a JavaScript object and used within the application. However, this is not the case in Elm since we have a strong type system. Before we can use JSON data, we need to convert it into a type defined in Elm. There is the [elm/json](https://package.elm-lang.org/packages/elm/json/latest/) package for that. + + +### Decoders + +Elm use decoders for that. It is a declarative way how to define what should be in the JSON and how to convert it into Elm types. Functions for that are defined in [Json.Decode](https://package.elm-lang.org/packages/elm/json/latest/Json-Decode) module. + +For example, we have this JSON representing a TODO: + +```json +{ + "id": 24, + "label": "Finish the home", + "completed": false +} +``` + +To get the `label` field, we can define a decoder like this: + +```elm +import Json.Decode as D exposing (Decoder) + +labelDecoder : Decoder String +labelDecoder = + D.field "label" D.string +``` + +There are functions to decode other primitives, like `bool` or `int`. However, we usually need more than just one field. We can combine decoders using `map` functions from `Json.Decode` module, e.g. `map3`. + +```elm +map3 : + (a -> b -> c -> value) + -> Decoder a + -> Decoder b + -> Decoder c + -> Decoder value +``` + +We can then define our own type for TODO and a decoder. + +```elm +import Json.Decode as D exposing (Decoder) + +type alias Todo = + { id : Int + , label : String + , completed : Bool + } + +todoDecoder : Decoder Todo +todoDecoder = + D.map3 Todo + (D.field "id" D.int) + (D.field "name" D.string) + (D.field "completed" D.bool) +``` + +There is a package [NoRedInk/elm-json-decode-pipeline](https://package.elm-lang.org/packages/NoRedInk/elm-json-decode-pipeline/latest) for more convenient JSON decoder. It is especially useful for large and more complex objects. We could rewrite the previous example using pipeline: + +```elm +import Json.Decode as D exposing (Decoder) +import Json.Decode.Pipeline exposing (required) + +type alias Todo = + { id : Int + , label : String + , completed : Bool + } + +todoDecoder : Decoder Todo +todoDecoder = + D.succeed Todo + |> required "id" D.int + |> required "name" D.string + |> required "completed" D.bool +``` + +It is not that big change in this case, however, we only have `map8` function in `Json.Decode` so this library comes handy if we need more. Moreover, it has other functions to define for example [optional](https://package.elm-lang.org/packages/NoRedInk/elm-json-decode-pipeline/latest/Json-Decode-Pipeline#optional) or [hardcoded](https://package.elm-lang.org/packages/NoRedInk/elm-json-decode-pipeline/latest/Json-Decode-Pipeline#hardcoded) values. + + +### Encoders + +When we want to send something to an API we need to do the opposite -- turn the Elm value into JSON value. We use functions form [Json.Encode](https://package.elm-lang.org/packages/elm/json/latest/Json-Encode) package for that. There is a type called `Value` which represents a JavaScript value and functions to convert Elm primitives, lists and objects into `Value` type. + +Here's an example using the TODO from decoders example. + +```elm +import Json.Encode as E + +type alias Todo = + { id : Int + , label : String + , completed : Bool + } + +encodeTodo : Todo -> E.Value +encodeTodo todo = + E.object + [ ( "id", E.int todo.id ) + , ( "label", E.string todo.label ) + , ( "completed", E.bool todo.completed ) + ] +``` + +The object is representend as a list of key value tuples. + + +## Http + +There is [Http](https://package.elm-lang.org/packages/elm/http/latest/Http) module in [elm/http](https://package.elm-lang.org/packages/elm/http/latest/) package for making HTTP requests in Elm. The functions creating requests create a command for Elm runtime which defines what request should be made, what is the expected response and what message should be send to update function when the request is done. + +Here is an example for getting TODO using the decoder defined in previous section. + +```elm +import Http + +type Msg = GotTodo (Result Http.Error Todo) + +getTodo : Cmd Msg +getTodo = + Http.get + { url = "http://example.com/todo" + , expect = Http.expectJson GotTodo todoDecoder + } +``` + +The function `getTodo` creates a command with HTTP request that expect JSON to be returned and uses `todoDecoder` to get `Todo` type form the returned JSON. Once the request is finished, we get `GotTodo` message containing the `Result` with either `Http.Error` if the request failed or `Todo` if the request was successful. + +There are other functions we can use for expected response like `expectString` to get the string as is or `expectWhatever` when we don't really care about the response as long as it's ok. + +When we want to do a POST request we also need to define the body. Here's an example of posting TODO to the server, using encoder function from previous section. + +```elm +import Http + +type Msg = TodoSaved (Result Http.Error ()) + +postTodo : Todo -> Cmd Msg +postTodo todo = + Http.post + { url = "http://example.com/todo" + , body = Http.jsonBody <| encodeTodo todo + , expect = Http.expectWhatever TodoSaved + } +``` + +Of course, we can send different types of body, not just JSON, e.g., `stringBody` for plain string or `emptyBody` when we don't want to send anything. + +When we want to do a different type of request than GET and POST or we want to set headers, we need to use `Http.request` function (`Http.post` and `Http.get` are actually just a shorthand for calling `Http.request`). + +```elm +request : + { method : String + , headers : List Header + , url : String + , body : Body + , expect : Expect msg + , timeout : Maybe Float + , tracker : Maybe String + } + -> Cmd msg +``` + + +## Subscriptions + +[Subscirptions](https://package.elm-lang.org/packages/elm/core/latest/Platform-Sub) are used to tell Elm that we want to be informed if something happend (e.g., web socket message or clock tick). + + +Here's an example of subscriptions defining that a message `Tick` with current time should be send to update function every 1000 milliseconds. + + +```elm +import Time + + +type Msg = Tick Time.Posix + + +subscriptions : Model -> Sub Msg +subscriptions model = + Time.every 1000 Tick +``` + + +## Random + +There is a [Random](https://package.elm-lang.org/packages/elm/random/latest/Random) module in [elm/random](https://package.elm-lang.org/packages/elm/random/latest/) package for generating pseudo-random values in Elm. It defines a type called `Generator` which can be think of as a recipe for generating random values. + +Here is a definition of genrator for random numbers between 1 and 4. + +```elm +import Random exposing (Generator) + +randomGrade : Generator Int +randomGrade = + Random.int 1 4 +``` + +If we want to use it, we have two options. The first is using `generate` function to create a command. Then we got the generated value back with a defined message to our update function. Here's an example: + + +```elm +import Random exposing (Generator) + + +type Msg = NewGrade Int + +generateGrade : Cmd +generateGrade = + Random.generate NewGrade randomGrade +``` + +The other option is to use `step` functions. It requires the generator and also a `Seed` and returns a tuple with generated value and a new `Seed`. The initial seed can be hardcoded (but then the generated values are same each time we run the application), send to Elm via Flags (we'll cover those in the next lesson) or using `generate` function first to get the seed and then use it to generate other random values. + +```elm +import Random exposing (Seed) + + +generateGrade : Seed -> (Int, Seed) +generateGrade seed = + Random.step randomGrade seed +``` + +## Materials + +- [Examples - TODO List](https://github.com/MI-AFP/elm-examples/tree/master/todo) +- [Examples - Timer](https://github.com/MI-AFP/elm-examples/tree/master/timer) + +## Further Reading + +- [Commands and Subscriptions](https://guide.elm-lang.org/effects/) +- [Elm Europe 2017 - Evan Czaplicki - The life of a file](https://www.youtube.com/watch?v=XpDsk374LDE) diff --git a/tutorials/10_frontend-frp.md b/tutorials/10_frontend-frp.md deleted file mode 100644 index 01b7d19..0000000 --- a/tutorials/10_frontend-frp.md +++ /dev/null @@ -1,417 +0,0 @@ -# Frontend and FRP - -In the previous tutorial, we focused on web frameworks and especially on building a backend and some frontend generation by blaze or hastache on the server-side. This time, we will cover building frontend apps that are standalone or communicate with backend via (REST) API. At the end of this tutorial, there is a section about a very interesting concept *Functional Reactive Programming* that is important when building purely functional user interfaces. - -## Haskell and Haskell-like frontends - -### The JavaScript Problem - -We all know what is JavaScript -- it is a dynamic, weakly typed, prototype-based and multi-paradigm programming language. Together with HTML and CSS, it is one of the three core technologies of the World Wide Web. JavaScript is used for interactive web pages and thus is an essential part of modern web applications. These days, JavaScript is often also used for the server-side or even desktop applications (e.g. [Atom editor](https://atom.io/)). - -As obvious from above, we need JavaScript. On the other hand, JavaScript has some issues that make working with it inconvenient and make developing software harder. Some things are improving with time (newer versions of [ECMAScript](https://en.wikipedia.org/wiki/ECMAScript)) but most of them remain from the very basic principles of the language: weak-typing, late binding, [weird automatic conversions](https://youtu.be/ryJSRZzAvUs), `this` behaviour, and lack of static types. There are solutions in the form of "improved syntax" like [CoffeeScript](http://coffeescript.org) and [TypeScript](https://www.typescriptlang.org) that are dealing with some of those... - -But since we are now Haskellists, we would like to have something even better - a Haskell-like JavaScript to solve these problems. Luckily, we are not the only ones and there are already several solutions how to compile Haskell to JavaScript or similar languages based on Haskell that are adapted for this very specific purpose. - -Take a look at [Slant - What are the best solutions to "The JavaScript Problem"?](https://www.slant.co/topics/1515/~solutions-to-the-javascript-problem). We are going to look at some now! - -### GHCJS - -GHCJS is a Haskell to JavaScript compiler that uses the GHC API. - -GHCJS supports many modern Haskell features, including: - - * All type system extensions supported by GHC - * Lightweight preemptive threading with blackholes, MVar, STM, asynchronous exceptions - * Weak references, CAF deallocation, StableName, StablePtr - * Unboxed arrays, emulated pointers - * Integer support through [JSBN](http://www-cs-students.stanford.edu/~tjw/jsbn/), 32 and 64 bit signed and unsigned arithmetic (`Word64`, `Int32` etc.) - * Cost-centres, stack traces - * Cabal support, GHCJS has its own package database - -And some JavaScript-specific features: - - * new JavaScriptFFI extension, with convenient import patterns, asynchronous FFI and a JSVal FFI type, - * synchronous and asynchronous threads. - -- Project: [ghcjs/ghcjs](https://github.com/ghcjs/ghcjs) -- Nice example: [Full stack web Haskell with Servant and GHCJS](http://blog.wuzzeb.org/full-stack-web-haskell/index.html) - -The dark side of GHCJS is big resulting JavaScript code and it is hard to make work (just try for yourself ;-). Also, the community around GHCJS and its ecosystem is not the most active one. - -### Haste - -[Haste](https://haste-lang.org) is an implementation of the Haskell functional programming language, geared towards web applications. Haste is based on the GHC, which means that it supports the full Haskell language, including GHC extensions and produces highly optimized code but comes with an extended set of standard libraries. However, compared to GHCJS, Template Haskell is not supported. Haste supports modern web technologies such as WebSockets, LocalStorage, Canvas, etc. out of the box. In addition, Haste comes pre-packaged with facilities for preemptive multitasking, working with binary data and other niceties. - -A Haste program can be compiled into a single JavaScript file, much like traditional browser-side programs, or into a JavaScript file and a server-side binary, with strongly typed communication between the two. In essence, Haste lets you write your client-server web application as a single, type-safe program, rather than two separate programs that just happen to talk to each other over some web API as is traditional. - -You don’t need to throw away all of your old code to start using Haste. In addition to the standard Haskell FFI, Haste provides its own flexible mechanism for easy Haskell-JavaScript integration, using fancy type magic to allow data of any type to be used by both Haskell and JavaScript code with minimal effort. - -Haste programs are more compact to GHCJS. While a certain increase in code size over hand-rolled JavaScript is unavoidable, an optimized but uncompressed Haste program is normally less than 3x the size of an equivalent hand-written program and the compiler takes special care to produce minifiable code, making the latency penalty of using Haste minimal. Sadly, similarly to GHCJS, the project is not much active (although not dead), the ecosystem, documentation and tutorials are not rich. - -- Examples: [valderman/haste-compiler](https://github.com/valderman/haste-compiler/tree/master/examples) -- API doc: [haste-compiler-0.5.5.0: Haskell To ECMAScript compiler](https://haste-lang.org/docs/haddock/0.5.5/) -- Our example: [DataStewardshipWizard/ds-wizard](https://github.com/DataStewardshipWizard/ds-wizard) and [DataStewardshipWizard/ds-form-engine](https://github.com/DataStewardshipWizard/ds-form-engine), built around JQuery bindings. - -### Miso - -**Miso** is a small "[isomorphic](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/)" [Haskell](https://www.haskell.org/) front-end framework for quickly building highly interactive single-page web applications. It features a virtual-dom, diffing / patching algorithm, attribute, and property normalization, event delegation, event batching, SVG, Server-sent events, Websockets, type-safe [servant](https://haskell-servant.github.io/)-style routing and an extensible Subscription-based subsystem. Inspired by [Elm](http://elm-lang.org/), [Redux](http://redux.js.org/) and [Bobril](http://github.com/bobris/bobril). **Miso** is pure by default, but side effects (like `XHR`) can be introduced into the system via the `Effect` data type. **Miso** makes heavy use of the [GHCJS](https://github.com/ghcjs/ghcjs) FFI and therefore has minimal dependencies. **Miso** can be considered a shallow [embedded domain-specific language](https://wiki.haskell.org/Embedded_domain_specific_language) for modern web programming. ([dmjio/miso](https://github.com/dmjio/miso/edit/master/README.md)) - -### PureScript - -PureScript is a strict, purely functional programming language inspired by Haskell which compiles to readable JavaScript with a simple foreign function interface and no runtime dependency. PureScript is sometimes called "Haskell done right" -- it learned from the long Haskell history, took the best of it (type system, category theory), refactored some parts, mostly basic libraries structure (Prelude) and added some goodies (records and row polymorphism) -- here is the overview of [differences](https://github.com/purescript/documentation/blob/master/language/Differences-from-Haskell.md). - -- Website: [purescript.org](http://www.purescript.org) -- Guide: [leanpub.com/purescript](https://leanpub.com/purescript/read) - -Although the ecosystem and documentation is considerably better than GHCJS's and Haste's, it has not reached broader adoption, (yet?). - -### Elm - -Elm is a functional language that compiles to JavaScript. It is not a Haskell, but language inspired by and in some ways very similar to Haskell (see [main differences](https://gist.github.com/cobalamin/c1b83f5626df1409b512ce2faf05cf84)) - it is more different from Haskell than PureScript. It offers an interesting type-safe alternative to projects such as React as a tool for creating websites and web apps. Elm has a very strong emphasis on simplicity, ease-of-use, and quality tooling. The compiler of Elm is written in Haskell and you can work with Elm in Haskell with [Language.Elm](https://hackage.haskell.org/package/Elm). - -- Guide: [guide.elm-lang.org](https://guide.elm-lang.org) -- Examples: [elm-lang.org/examples](http://elm-lang.org/examples) -- Our example: [DataStewardshipWizard/dsw-client](https://github.com/DataStewardshipWizard/dsw-client) - -Simplicity, good ecosystem, documentation and active community earned Elm quite some interest. At the same time, the lack of type classes hinders flexibility, reuse and DRY. - -### ReasonML - -[ReasonML](https://reasonml.github.io/) is a notable project that should be mentioned here. Although it is not based on Haskell, it shares the same ancestor -- the [ML language](https://en.wikipedia.org/wiki/ML_(programming_language)), the first FP language with Hindley-Milner type system that influenced many today's languages. ML (and its newer dialects Standard ML, Caml and OCaml) are not entirely pure (and as such they are being scorned by Haskellists ;-), but you find a strong type system, ADTs, etc. there, as well. - -What makes ReasonML notable is that it was created by Facebook and thus gets a strong warp. 50% of FB Messanger has been rewritten into ReasonML and there are quite some [impressive statistics](https://reasonml.github.io/blog/2017/09/08/messenger-50-reason.html). It is a project that is worth at least to be observed. - -## FRP - Functional Reactive Programming - -Functional reactive programming (FRP) is a programming paradigm for asynchronous dataflow programming using the building blocks of functional programming (such as `map`, `filter`, `fold`s, higher-order functions, etc.). It has been used often for programming graphical user interfaces (GUIs), robotics, and music, aiming to simplify these problems by explicitly modelling the concept of time. A good example to imagine what is it about is an ordinary spreadsheet calculator (Excel). You have cells that compute something from different cells and when you edit some, the related will recalculate - you do not tell what should be recalculated nor recalculate by yourself, all changes automatically propagate. See? It is "action and reaction"! - -The original idea was introduced more than 20 years ago by Conal Elliott in his [paper](http://conal.net/papers/ActiveVRML/ActiveVRML.pdf). Since then, he published several [other papers](http://conal.net/papers/) and there are also videos about the [FRP essence](https://begriffs.com/posts/2015-07-22-essence-of-frp.html). Several different trade-off approaches has been developed for practical usage. You can see the list [here](https://wiki.haskell.org/Functional_Reactive_Programming#Libraries). Also, of course, you can find FRP implementations in other languages than Haskell. - -### FRP principles - -For better understanding what FRP is about and what are the basic concepts, please read [The introduction to Reactive Programming you've been missing (by @andrestaltz)](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)... - -### Reactive - -[Reactive](https://hackage.haskell.org/package/reactive) is a simple foundation for programming reactive systems functionally. Like Fran/FRP, it has a notion of (reactive) behaviours and events. Unlike most previous FRP implementations, Reactive has a hybrid demand/data-driven implementation, as described in the paper "Push-pull functional reactive programming", http://conal.net/papers/push-pull-frp/. - -Sadly the documentation, tutorials, and examples are not currently in a good shape. - -### Reactive-banana - -[Reactive-banana](https://wiki.haskell.org/Reactive-banana) is meant to be used in conjunction with existing libraries that are specific to your problem domain. For instance, you can hook it into any event-based GUI framework, like `wxHaskell` or `Gtk2Hs`. Several helper packages such as `reactive-banana-wx` provide a small amount of glue code that can make life easier. - -The goal of the library is to provide a solid foundation. - -* Programmers interested implementing FRP will have a reference for a simple semantics with a working implementation. The library stays close to the semantics pioneered by Conal Elliott. -* The library features an efficient implementation. No more spooky time leaks, predicting space & time usage should be straightforward. -* A plethora of [example code](https://wiki.haskell.org/Reactive-banana/Examples) helps with getting started. - -### Yampa - -[Yampa](https://wiki.haskell.org/Yampa) is a domain-specific embedded language for programming of hybrid (discrete and continuous time) systems using the concepts of FRP. Yampa is structured using Arrows, which greatly reduce the chance of introducing space- and time-leaks into reactive, time-varying systems. - -![Signals in Yampa](https://wiki.haskell.org/wikiupload/thumb/1/10/Yampa_signal_functions.svg/624px-Yampa_signal_functions.svg.png) - -## Reactive programming with Elm - -We introduced Elm as a "convenient language" and it also supports reactive programming, so let us show a simple example to demonstrate the clean [architecture of Elm apps](https://guide.elm-lang.org/architecture/) - Model, View, and Update. This app will be just one module `Main` (although you can have multi-module apps just as in Haskell) and it will have 4 pages: - -1. Simple static landing -2. Unit converter for metres, yards, feet, and inches (reactive update: Update->Model->View cycle) -3. GitHub API info about user (demonstrate HTTP communication with some API) -4. Not found page as default - -You need to install [Node.js](https://nodejs.org/en/) and [Elm](https://guide.elm-lang.org/install.html). Then you can use `elm-repl` to try something (like GHCi), `elm-reactor` for development, `elm-package` to work with dependencies, and `elm-make` to build it into JavaScript. - -In this example we need some dependencies so the final `elm-package.json` looks like this: - -```json -{ - "version": "1.0.0", - "summary": "Example simple Elm project", - "repository": "https://github.com/MI-AFP/elm-example.git", - "license": "MIT", - "source-directories": [ - "." - ], - "exposed-modules": [], - "dependencies": { - "elm-lang/core": "5.1.1 <= v < 6.0.0", - "elm-lang/html": "2.0.0 <= v < 3.0.0", - "elm-lang/http": "1.0.0 <= v < 2.0.0", - "elm-lang/navigation": "2.1.0 <= v < 3.0.0", - "evancz/url-parser": "2.0.1 <= v < 3.0.0", - "rundis/elm-bootstrap": "4.0.0 <= v < 5.0.0" - }, - "elm-version": "0.18.0 <= v < 0.19.0" -} -``` - -```elm -module Main exposing (..) - -import Html exposing (..) -import Html.Attributes exposing (..) -import Html.Events exposing (onInput) -import Http -import Navigation exposing (Location) -import UrlParser exposing ((</>)) - --- Entrypoint (we use Navigation) -main : Program Never Model Msg -main = Navigation.program UrlChange - { view = view - , update = update - , subscriptions = (\_ -> Sub.none) - , init = init - } - --- Model = state of the app -type alias Model = - { page : Page - , metres : Float - , token : GitHubToken - , githubData : String - } - --- Page = enum of different views -type Page - = Home - | UnitConverter - | GitHubInfo - | NotFound - --- Own type for GitHub token -type GitHubToken - = Valid String - | Invalid String - --- Units for the conversion -type LengthUnit - = Metres - | Inches - | Yards - | Feets - --- Types of messages in the app with content type(s) -type Msg - = UrlChange Location - | UnitUpdate LengthUnit String - | TokenUpdate String - | GitHubResponse (Result Http.Error String) - --- Initial app state and command -init : Location -> ( Model, Cmd Msg ) -init location = urlUpdate location { page = Home - , metres = 0 - , token = Invalid "" - , githubData = "" - } - --- Update (when message comes, update model), this is just "router" -update : Msg -> Model -> (Model, Cmd Msg) -update msg model = - case msg of - UrlChange location -> - urlUpdate location model - UnitUpdate lu str -> - metresUpdate lu str model - TokenUpdate str -> - tokenUpdate str model - GitHubResponse res -> - githubUpdate res model - -githubUpdate : (Result Http.Error String) -> Model -> (Model, Cmd Msg) -githubUpdate res model = - case res of - Ok str -> ({ model | githubData = str }, Cmd.none) - Err _ -> ({ model | githubData = "Error!" }, Cmd.none) - -tokenUpdate : String -> Model -> (Model, Cmd Msg) -tokenUpdate str model = - if isTokenValid str then - ( { model | token = Valid str }, gitHubInfoRequest str ) - else - ( { model | token = Invalid str }, Cmd.none ) - --- Send request to GitHub and then it will send appropriate message in this app -gitHubInfoRequest : String -> Cmd Msg -gitHubInfoRequest token = - Http.send GitHubResponse <| Http.request - { method = "GET" - , headers = [ Http.header "Authorization" ("token " ++ token)] - , url = "https://api.github.com/user" - , body = Http.emptyBody - , expect = Http.expectString - , timeout = Nothing - , withCredentials = False - } - -isTokenValid : String -> Bool -isTokenValid str = String.length str == 40 - -metresUpdate : LengthUnit -> String -> Model -> ( Model, Cmd Msg ) -metresUpdate lu x model = - case String.toFloat x of - Ok v -> ( { model | metres = v / (unitCoefficient lu)}, Cmd.none ) - Err _ -> ( model, Cmd.none ) - -urlUpdate : Navigation.Location -> Model -> ( Model, Cmd Msg ) -urlUpdate location model = - case decode location of - Nothing -> - ( { model | page = NotFound }, Cmd.none ) - Just route -> - ( { model | page = route }, Cmd.none ) - -decode : Location -> Maybe Page -decode location = - UrlParser.parseHash routeParser location - -routeParser : UrlParser.Parser (Page -> a) a -routeParser = - UrlParser.oneOf - [ UrlParser.map Home UrlParser.top - , UrlParser.map UnitConverter (UrlParser.s "unit-converter") - , UrlParser.map GitHubInfo (UrlParser.s "github-info") - ] - ---> VIEW -view : Model -> Html Msg -view model = - div [] - [ menu model - , mainContent model - ] - -menu : Model -> Html Msg -menu model = - div [] - [ viewLink "" "Home" - , viewLink "unit-converter" "Unit Converter" - , viewLink "github-info" "GitHub Info" - ] - -viewLink : String -> String -> Html msg -viewLink slug name = - li [] [ a [ href ("#" ++ slug) ] [ text name ] ] - -mainContent : Model -> Html Msg -mainContent model = - div [] ( - case model.page of - Home -> - pageHome model - UnitConverter -> - pageUnitConverter model - GitHubInfo -> - pageGitHubInfo model - NotFound -> - pageNotFound - ) - -pageHome : Model -> List (Html Msg) -pageHome model = - [ h1 [] [ text "Home" ] - , p [] [ text "This is very simple Elm example" ] - , hr [] [] - , p [] [ text "Enjoy learning " - , a [href "http://elm-lang.org"] [text "Elm"] - , text "!" - ] - ] - -pageUnitConverter : Model -> List (Html Msg) -pageUnitConverter model = - [ h1 [] [ text "Unit Converter" ] - , hr [] [] - , makeUnitInput Metres model - , makeUnitInput Inches model - , makeUnitInput Feets model - , makeUnitInput Yards model - ] - -makeUnitInput : LengthUnit -> Model -> Html Msg -makeUnitInput lu model = - div [] - [ label [] [text (unitToString lu)] - , input [ type_ "number" - , onInput (UnitUpdate lu) - , value (toString (computeUnit lu model)) - ] - [] - ] - -pageGitHubInfo : Model -> List (Html Msg) -pageGitHubInfo model = - [ h1 [] [ text "GitHub Info" ] - , div [] - [ label [] [text "GitHub token: "] - , input [ type_ "text" - , onInput TokenUpdate - , value (tokenToString model.token) - ] - [] - ] - , case model.token of - Valid token -> githubInfo model - Invalid _ -> invalidTokenMsg - ] - -githubInfo : Model -> (Html Msg) -githubInfo model = - pre [] [text (model.githubData)] - - -invalidTokenMsg : (Html Msg) -invalidTokenMsg = - div [] - [ p [] [text "Your token is not valid (40 chars required)"] - ] - -tokenToString : GitHubToken -> String -tokenToString t = - case t of - Valid s -> s - Invalid s -> s - -pageNotFound : List (Html Msg) -pageNotFound = - [ h1 [] [ text "Not found" ] - , text "Sorry couldn't find that page" - ] - ---> LOGIC -unitToString : LengthUnit -> String -unitToString lu = - case lu of - Metres -> "Metres" - Inches -> "Inches" - Yards -> "Yards" - Feets -> "Feets" - -computeUnit : LengthUnit -> Model -> Float -computeUnit lu model = model.metres * (unitCoefficient lu) - -unitCoefficient : LengthUnit -> Float -unitCoefficient lu = - case lu of - Metres -> 1 - Inches -> 39.3700787 - Yards -> 1.0936133 - Feets -> 3.2808399 -``` - -You can play with this app from [MI-AFP/elm-example](https://github.com/MI-AFP/elm-example). - -## Task assignment - -The homework to create a simple frontend for a REST API described in the repository [MI-AFP/hw10](https://github.com/MI-AFP/hw10). - -## Further reading - -* [Haskell on the front end](https://www.reddit.com/r/haskell/comments/7ax2ji/haskell_on_the_front_end/) -* [Zdroják.cz - Elm (czech only)](https://www.zdrojak.cz/clanky/elm-uvod/) -* [gelisam/frp-zoo (FRP libs comparison)](https://github.com/gelisam/frp-zoo) -* [FRP explanation using reactive-banana](https://wiki.haskell.org/FRP_explanation_using_reactive-banana) diff --git a/tutorials/11_elm-building-web-apps.md b/tutorials/11_elm-building-web-apps.md new file mode 100644 index 0000000..255fefc --- /dev/null +++ b/tutorials/11_elm-building-web-apps.md @@ -0,0 +1,350 @@ +# Elm - Building Web Applications + +## Webpack + +[Webpack](https://webpack.js.org) is a module bundler for JavaScript applications. It builds a dependency graph of source modules and creates static assets. It uses different loaders for different file types, e.g., to convert new EcmaScript syntax into the one supported by browsers or to convert Sass to CSS. It can also minify those assets. + +Besides other loaders, there is also [elm-webpack-laoder](https://github.com/elm-community/elm-webpack-loader). If we require an Elm module from JavaScript code, it will use `elm make` under the hood to build it. + +Here's an example of configuration: + +```js +module.exports = { + module: { + rules: [{ + test: /\.elm$/, // use the loader only for elm files + exclude: [/elm-stuff/, /node_modules/], // exclude project dependencies + use: { + loader: 'elm-webpack-loader', + options: {} + } + }] + } +}; +``` + +## Initializing Elm App + +Till now, we used `elm reactor` to run our Elm applications. However, we can't use that in a production environment. If we try to build the Todo list form [elm-examples](https://github.com/MI-AFP/elm-examples) using `elm make`, we can look into the generated JavaScript, how the app is initialized. At the very end of the document, there is the following line of code: + +```js +var app = Elm.Todos.init({ node: document.getElementById("elm-f0111bc4e658d0f98db96260c16f7e49") }); +``` + +Todos is the name of the module (it was in `src/Todos.elm`). The `init` function is called to initialize the app. We used [Browser.element](https://package.elm-lang.org/packages/elm/browser/latest/Browser#element), therefore there is `node` in init arguments. + +When we are using webpack, we can do something very similar ourselves. We need a JavaScript entrypoint, e.g., `index.js`. We can require an Elm module from there, and the elm-webpack-loader will take care of compiling Elm into JavaScript. Then, we can call the init function. + +```js +// index.js +var program = require('src/Todos.elm'); + +var app = program.Elm.Todos.init({ + node: document.getElementById('node-id') +}); +``` + +We also need to create the element in our HTML structure. In case we would use [Browswer.document](https://package.elm-lang.org/packages/elm/browser/latest/Browser#document) or [Browser.application](https://package.elm-lang.org/packages/elm/browser/latest/Browser#application) instead, we don't need to specify the node since it will load into the whole document body. + + +## JavaScript Interop + +In real-world applications, we usually need at least some interoperability with JavaScript. There are two options in Elm -- using flags or ports. + +### Flags + +Flags are used to pass some values when initializing the Elm app. For example, we can send a random initial seed generated using JavaScript. The object we pass to the `init` function can have another property called `flags`. + +```js +// index.js +var program = require('src/Todos.elm'); + +var app = program.Elm.Todos.init({ + node: document.getElementById('node-id'), + flags: Math.random() +}); +``` + +Then, on the Elm side, we receive these flags as the first argument of `init` function. + +```elm +init : Float -> (Model, Cmd Msg) +init randomNumber = + ... +``` + +The flags can be one of the following types: + +- Basic types (`Bool`, `Int`, `Float`, `String`) +- `Maybe` +- Lists (`List`, `Array`) +- Tuples +- Records +- `Json.Decode.Value` + +If we use anything else than `Json.Decode.Value` and provide an incorrect type, we get an error on the JavaScript side. Therefore it is safer to use `Json.Decode.Value`, define a decoder and handle possible errors on the Elm side when the decoding fails. + +### Ports + +While Flags are used for sending initial values to the Elm app, Ports are used for sending messages between JavaScript and Elm. The communication is not based on request/response like in HTTP though. We only have one-way messages send from Elm to JavaScript or vice versa. + +#### Outgoing Messages + +Sending messages from Elm to JavaScript is realized through commands. We need to define the `port` using a `port module`. + +A common example is using localStorage, so for example, here we want to save user data into localStorage. + +```elm +port module Ports exposing (saveUser) + +import Json.Encode as E + +port saveUser : E.Value -> Cmd msg +``` + +The port declaration is similar to a function. However, it starts with a keyword `port` and has no function body. The module where we define ports must be a `port module`. + +Once we define the port for outgoing messages, we should subscribe to it on the JavaScript side after the app initialization. + +```js +var program = require('src/Todos.elm'); + +var app = program.Elm.Todos.init({ + node: document.getElementById('node-id') +}); + +app.ports.saveUser.subscribe(function(data) { + localStorage.setItem('user', JSON.stringify(data)); +}); +``` + +And then we can create a command using the port in our update function. + +```elm +import Ports exposing (saveUser) + + +update msg model = + case msg of + SaveUser user -> + (model, saveUser <| encdeUser user) + ... +``` + +#### Incoming messages + +Sending messages from JavaScript to Elm is realized through Subscriptions. We need to define the port but this time slightly different again. + +For example, we want to send user data back to the Elm app. + +```elm +port module Ports exposing (gotUser) + +import Json.Encode as E + +port gotUser : (E.Value -> msg) -> Sub msg +``` + +Then in our subscriptions function, we need to subscribe to use the port with proper message constructor. + +```elm +import Json.Encode as E +import Ports exposing (gotUser) + + +type Msg + = GotUser E.Value + +subscriptions model = + gotUser GotUser + +``` + +On JavaScript side, we can send a message to the Elm app using the port. + +```js +var program = require('src/Todos.elm'); + +var app = program.Elm.Todos.init({ + node: document.getElementById('node-id') +}); + + +app.ports.gotUser.send(userData); +``` + +## Single Page Applications + +When we are building real applications, we usually need more than one screen. We also want to have different URLs for different screens, so it is, for example, easier to send links to the application. We need to handle navigation and URL parsing. + +### Navigation + +We use [Browser.application](https://package.elm-lang.org/packages/elm/browser/latest/Browser#application) which avoids loading new HTML when URL changes. It has more complex annotation: + +```elm +application : + { init : flags -> Url -> Key -> ( model, Cmd msg ) + , view : model -> Document msg + , update : msg -> model -> ( model, Cmd msg ) + , subscriptions : model -> Sub msg + , onUrlRequest : UrlRequest -> msg + , onUrlChange : Url -> msg + } + -> Program flags model msg +``` + +The `init` function now gets not only flags but also initial `Url` and navigation `Key` that is needed for changing URL using navigation commands. + +When a link is clicked within the application, it is intercepted as `UrlRequest`, the message is created using `onUrlRequest` and sent to the update function. + +When the URL is changed, the message is created using `onUrlChange` and sent to the update function. + +Here is a simple example from [An Introduction to Elm](https://guide.elm-lang.org/webapps/navigation.html). + +```elm +import Browser +import Browser.Navigation as Nav +import Html exposing (..) +import Html.Attributes exposing (..) +import Url + + + +-- MAIN + + +main : Program () Model Msg +main = + Browser.application + { init = init + , view = view + , update = update + , subscriptions = subscriptions + , onUrlChange = UrlChanged + , onUrlRequest = LinkClicked + } + + + +-- MODEL + + +type alias Model = + { key : Nav.Key + , url : Url.Url + } + + +init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +init flags url key = + ( Model key url, Cmd.none ) + + + +-- UPDATE + + +type Msg + = LinkClicked Browser.UrlRequest + | UrlChanged Url.Url + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + LinkClicked urlRequest -> + case urlRequest of + Browser.Internal url -> + ( model, Nav.pushUrl model.key (Url.toString url) ) + + Browser.External href -> + ( model, Nav.load href ) + + UrlChanged url -> + ( { model | url = url } + , Cmd.none + ) + + + +-- SUBSCRIPTIONS + + +subscriptions : Model -> Sub Msg +subscriptions _ = + Sub.none + + + +-- VIEW + + +view : Model -> Browser.Document Msg +view model = + { title = "URL Interceptor" + , body = + [ text "The current URL is: " + , b [] [ text (Url.toString model.url) ] + , ul [] + [ viewLink "/home" + , viewLink "/profile" + , viewLink "/reviews/the-century-of-the-self" + , viewLink "/reviews/public-opinion" + , viewLink "/reviews/shah-of-shahs" + ] + ] + } + + +viewLink : String -> Html msg +viewLink path = + li [] [ a [ href path ] [ text path ] ] +``` + + +### URL Parsing + +In the previous example, we used Url as is. However, we can use [Url.Parser](https://package.elm-lang.org/packages/elm/url/latest/Url-Parser) module from [elm/url](https://package.elm-lang.org/packages/elm/url/latest/) package to parse the URL into useful Elm types. + + +Here's an example form the documentation converting different routes with parameters into `Route` type. + +```elm +type Route + = Topic String + | Blog Int + | User String + | Comment String Int + +route : Parser (Route -> a) a +route = + oneOf + [ map Topic (s "topic" </> string) + , map Blog (s "blog" </> int) + , map User (s "user" </> string) + , map Comment (s "user" </> string </> s "comment" </> int) + ] + +-- /topic/wolf ==> Just (Topic "wolf") +-- /topic/ ==> Nothing + +-- /blog/42 ==> Just (Blog 42) +-- /blog/wolf ==> Nothing + +-- /user/sam/ ==> Just (User "sam") +-- /user/bob/comment/42 ==> Just (Comment "bob" 42) +-- /user/tom/comment/35 ==> Just (Comment "tom" 35) +-- /user/ ==> Nothing +``` + + +## Materials + +- [elm-webpack-boilerplate](https://github.com/MI-AFP/elm-webpack-boilerplate) +- [Examples - Web Application](https://github.com/MI-AFP/elm-examples/tree/master/webapp) + +## Further Reading + +- [Webpack Concepts](https://webpack.js.org/concepts) +- [Web Apps · An Introduction to Elm](https://guide.elm-lang.org/webapps/) +- [Elm Europe 2017 - Richard Feldman - Scaling Elm Apps](https://www.youtube.com/watch?v=DoA4Txr4GUs) diff --git a/tutorials/12_elm-real-world-use-cases.md b/tutorials/12_elm-real-world-use-cases.md new file mode 100644 index 0000000..0fdad35 --- /dev/null +++ b/tutorials/12_elm-real-world-use-cases.md @@ -0,0 +1,297 @@ +# Elm - Real World Use Cases + +## Complex Forms + +Sometimes, we need more complex forms in our application. Using common Elm approach with a message per input field can result in a lot of boilerplate code. Luckily there are some libraries to help with that. One of them is [etaque/elm-form](https://package.elm-lang.org/packages/etaque/elm-form/latest). +At the price of losing some type safety (field names are using strings), we get nice validation API similar to JSON decoders API with some basic validation and a possibility to create our own, support for nested fields and lists. + +First, we need to define a type that should be represented in our form, e.g., a person. + +```elm +type alias Person = + { name : String + , age : Int + } +``` + +Then, we define validation (notice that the API is basically the same as using `Json.Decode` module): + +```elm +import Form.Validate as Validate exposing (..) + + +personValidation : Validation CustomFormError Person +personValidation = + Validate.map2 Person + (Validate.field "name" Validate.string) + (Validate.field "age" Validate.int) +``` + +After that, we can initialize the form, either empty: + +```elm +initPersonForm : Form CustomFormError Person +initPersonForm = + Form.initial [] personValidation +``` + +Or using existing person data (notice that the API for the initial data is basically the same as using `Json.Encode` module): + +```elm +import Form.Field as Field + + +initPersonFormWithPerson : Person -> Form CustomFormError Person +initPersonFormWithPerson person= + let + initials = + [ ( "name", Field.string person.name ) + , ( "age", Field.int person.age ) + ] + in + Form.initial initials personValidation +``` + +The library comes with functions for generating views. We need to create our own functions for converting form errors to string (because we can add our own errors). Also, all fields are represented as string or bool (for checkboxes), so we cannot really use for example input type number. + +```elm +import Form exposing (Form) +import Form.Input as Input + + +viewForm : Form () Person -> Html Form.Msg +viewForm form = + let + viewError field = + case field.liveError of + Just error -> + p [ class "error" ] [ text (errorToString error) ] + + Nothing -> + text "" + + nameField = + Form.getFieldAsString "name" form + + ageField = + Form.getFieldAsString "age" form + in + form [ onSubmit Form.Submit ] + [ label [] [ text "name" ] + , Input.textInput nameField [] + , viewError nameField + , label [] [ text "age" ] + , Input.textInput ageField [] + , viewError ageField + , button [ type_ "submit" ] [ text "Submit" ] + ] +``` + +The last thing is handling Form messages. If the message is `Form.Submit`, and the form is valid, we want to handle submitting the form. To get the output, there is a function `Form.getOutput`, it either returns `Just` the type in the form or `Nothing` if the form contains invalid values. + +We want to use `Form.update` function for other messages to update the form model. + +```elm +type Msg + = FormMsg Form.Msg + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + FormMsg formMsg -> + case ( formMsg, Form.getOutput model.personForm ) of + ( Form.Submit, Just form ) -> + -- The output is the original type (Person in this case) + -- We get it only if the validation passes + let + _ = + Debug.log "Submit form" form + in + ( model, Cmd.none ) + + _ -> + ( { model | personForm = Form.update personValidation formMsg model.personForm } + , Cmd.none + ) +``` + + +## SVG + +Elm has a package [elm/svg](https://package.elm-lang.org/packages/elm/svg/latest/) for creating SVG images in Elm. The API looks like corresponding packages for Html. Now we have `Svg` module with functions for SVG elements (e.g., `rect` or `circle`), `Svg.Attributes` for the attributes used by SVG elements (e.g., `strokeWidth` or `x`) and `Svg.Events` for JavaScript events, same as `Html.Events`. + +SVG is good for visualisations. We can start at [SVG element reference](https://developer.mozilla.org/en-US/docs/Web/SVG/Element) to find the elements we need. Here's an example from package documentation of drawing a rounded rectangle: + +```elm +import Svg exposing (..) +import Svg.Attributes exposing (..) + + +roundRect = + svg + [ width "120", height "120", viewBox "0 0 120 120" ] + [ rect [ x "10", y "10", width "100", height "100", rx "15", ry "15" ] [] ] +``` + + +## Files + +Since Elm 0.19 there is [elm/file](https://package.elm-lang.org/packages/elm/file/latest/) package for working with files. We can use it for allowing users to download a file generated in Elm, or for uploading files. + + +Example of download a markdown file generated from a string in Elm: + +```elm +import File.Download as Download + +save : String -> Cmd msg +save markdown = + Download.string "draft.md" "text/markdown" markdown +``` + +Example of file select: + +```elm +import File.Select as Select + +type Msg + = ImagesRequested + | ImagesLoaded File (List File) + +requestImages : Cmd Msg +requestImages = + Select.files ["image/png","image/jpg"] ImagesLoaded +``` + +If we get `File` from the previous example, we can send it to the server using [elm/http](https://package.elm-lang.org/packages/elm/http/2.0.0/) package. There is a [fileBody](https://package.elm-lang.org/packages/elm/http/latest/Http#fileBody) function for that. We can explore [examples](https://github.com/elm/file/tree/master/examples) in elm/file package to see how it works. + + +## Graph QL + +There is a couple of packages for working with GraphQL in Elm. + +### [dillonkearns/elm-graphql](https://github.com/dillonkearns/elm-graphql/tree/4.2.1) + +This is one of the most popular GraphQL packages for Elm. It comes with a command-line code generator to create type-safe Elm code for GraphQL endpoint. Then, we get type-safe GraphQL queries. It is also eliminating GraphQL features in favour of Elm language constructs. + +Here's an example from the documentation. + +The GraphQL query: + +```graphql +query { + human(id: "1001") { + name + homePlanet + } +} +``` + +It looks like this in the Elm code (`StarWars` packages are auto-generated using the command line tool): + +```elm +import Graphql.Operation exposing (RootQuery) +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) +import StarWars.Object.Human as Human +import StarWars.Query as Query +import StarWars.Scalar exposing (Id(..)) + + +query : SelectionSet (Maybe Human) RootQuery +query = + Query.human { id = Id "1001" } humanSelection + + +type alias Human = + { name : String + , homePlanet : Maybe String + } + + +humanSelection : SelectionSet Human StarWars.Object.Human +humanSelection = + SelectionSet.map2 Human + Human.name + Human.homePlanet +``` + +### [jamesmacaulay/elm-graphql](https://package.elm-lang.org/packages/jamesmacaulay/elm-graphql/latest) + +This is another popular package for GraphQL. It provides an interface for working with GraphQL queries and schemas in Elm. Building requests works similarly as JSON decoders. + +Here's an example from the documentation, we need to define the types by ourselves: + +```elm +type alias Photo = + { url : String + , caption : String + } + +type alias User = + { name : String + , photos : List Photo } +``` + +Then we build a query document using the library: + +```elm +userQuery : Document Query User { vars | userID : String } +userQuery = + let + userIDVar = + Var.required "userID" .userID Var.id + + photo = + object Photo + |> with (field "url" [] string) + |> with (field "caption" [] string) + + user = + object User + |> with (field "name" [] string) + |> with (field "photos" [] (list photo)) + + queryRoot = + extract + (field "user" + [ ( "id", Arg.variable userIDVar ) ] + user + ) + in + queryDocument queryRoot +``` + +The document would be encoded into this string: + +```graphql +query ($userID: ID!) { + user(id: $userID) { + name + photos { + url + caption + } + } +} +``` + + +## WebSockets + +There is a package [elm-lang/websockets](https://package.elm-lang.org/packages/elm-lang/websocket/latest), however, it was **not yet updated** to Elm 0.19. It [should be updated](https://discourse.elm-lang.org/t/updating-packages/1771) at some point in the future. There is a 3rd party package [billstclair/elm-websocket-client](https://package.elm-lang.org/packages/billstclair/elm-websocket-client/latest/) that is converting the original package to Elm 0.19. + +The other option is to [use ports](https://stackoverflow.com/a/52569683/2492795) and to implement WebSocket interactions on the JavaScript side. + + +## Materials + +- [Examples - Complex Form](https://github.com/MI-AFP/elm-examples/tree/master/form) +- [Examples - SVG Clock](https://github.com/MI-AFP/elm-examples/tree/master/clock) + +## Further Reading + +- [SVG: Scalable Vector Graphics](https://developer.mozilla.org/en-US/docs/Web/SVG) +- [Line Charts - A library for plotting line charts in SVG. Written in all Elm.](https://github.com/terezka/line-charts) +- [Working with Files](https://elm-lang.org/blog/working-with-files) +- [Type-Safe & Composable GraphQL in Elm](https://medium.com/open-graphql/type-safe-composable-graphql-in-elm-b3378cc8d021) +- [Elm Port Examples](https://github.com/MattCheely/elm-port-examples) diff --git a/tutorials/97_monadtrans.md b/tutorials/97_monadtrans.md new file mode 100644 index 0000000..1061889 --- /dev/null +++ b/tutorials/97_monadtrans.md @@ -0,0 +1,411 @@ +# More on Monad Transformers + +In Haskell and in functional programming in general, a monad transformer is a type constructor which takes a monad as an argument and returns a monad as a result. Basically said, it is combination of monads in "layers". + +Monad transformers can be pretty useful and make code both efficient and readable when used properly. They can be used to compose features encapsulated by monads – such as state, exception handling, I/O, logging, and others – in a modular way. Typically, a monad transformer is created by generalising an existing monad and applying the resulting monad transformer to the `Identity` or `IO` monad yields a monad which is equivalent to the original monad (ignoring any necessary boxing and unboxing). + +## Definition + +Monad transformer is a wrapper type that has: + +1. a type constructor `t` of kind `(* -> *) -> * -> *` (needs a monad `m` a type) +2. defined monad operations `return` and `>>=` (bind) compliant in mo nadic laws for `t m` iff `m` is a monad +3. an operation `lift` of type `m a -> t m a` that allows to access the inner monad `m` and fullfils: + * `lift . return = return` + * `lift (m >>= k) = (lift m) >>= (lift . k)` + +## MonadT, MaybeT and EitherT + +When using monad transformers in Haskell, package [transformers](http://hackage.haskell.org/package/transformers) provide them for basic monads such as `Maybe`, `List`, `Reader`, `Writer`, or `State`. Be careful, others are separately in different packages such as `Either` in [either](https://hackage.haskell.org/package/either). It is quite common to use `T` as suffix after monad name to name its transformer. + +### Maybe with IO + +As was already said, monad transformers are used as wrapper types to combine monads with other common features provided by different monads, for example, `IO` with actions like `putStrLn` or `getLine`. Why would someone need such a combination? Why not use those monads in a normal way? + +```haskell +main :: IO +main = do + maybeUserName <- readUserName + case maybeUserName of + Nothing -> print “Invalid user name!” + Just (uName) -> do + maybeEmail <- readEmail + case maybeEmail of + Nothing -> print “Invalid email!” + Just (email) -> do + maybePassword <- readPassword + Case maybePassword of + Nothing -> print “Invalid Password” + Just password -> login uName email password + +readUserName :: IO (Maybe String) +readUserName = do + str <- getLIne + if length str > 5 + then return $ Just str + else return Nothing + +readEmail :: IO (Maybe String) +... + +readPassword :: IO (Maybe String) +... + +login :: String -> String -> String -> IO () +... +``` + +By using monad transformer `MaybeT IO` that wraps `IO` actions the code can get much more readable and efficient. It is also better in terms of extensibility and modularity when you want to replace `IO`, for example. + +```haskell +newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } + +instance (Monad m) => Monad (MaybeT m) where + return = lift . return + x >>= f = MaybeT $ do + v <- runMaybeT x + case v of + Nothing -> return Nothing + Just y -> runMaybeT (f y) +``` + +It is just a newtype that inserts maybe into some monad. Function `return` is then just lifted for this monad transformer and bind `>>=` works naturally for `Maybe` type, returns `Nothing` if `Nothing` is wrapped and evaluates with value from `Just` otherwise. + +```haskell +readUserName :: MaybeT IO String +readUserName = MaybeT $ do + str <- getLine + if length str > 5 + then return $ Just str + else return Nothing +``` + +The code is then pretty readable and understandable on the first sight, no more bothersome nested `if`s or `case`s. It is also better for maintenance obviously. Similarly it works for `EitherT` where it is important that in the inner monad (such as `IO`) is only the type of `Right` values and not `Left`s. + +```haskell +newtype EitherT e m a = EitherT { runEitherT :: m (Either e a) } + +instance Monad m => Monad (EitherT e m) where + return a = EitherT $ return (Right a) + m >>= k = EitherT $ do + a <- runEitherT m + case a of + Left l -> return (Left l) + Right r -> runEitherT (k r) +``` + +## More Layers of Monads + +Monad transformers are as shown again monads, that means that you can wrap monad transfomers in monad transformers! + +```haskell +type Env = (Maybe String, Maybe String, Maybe String) + +readUserName :: MaybeT (ReaderT Env IO) String +readUserName = MaybeT $ do + (maybeOldUser, _, _) <- ask + case maybeOldUser of + Just str -> return str + Nothing -> do + -- lift allows normal IO functions from inside ReaderT Env IO! + input <- lift getLine + if length input > 5 + then return (Just input) + else return Nothing +``` + +But as you can see that to get into inner layers more lifts are needed... In such cases, some helper functions and naming your transformer is quite handy. + +```haskell +type TripleMonad a = MaybeT (ReaderT Env IO) a + +performReader :: ReaderT Env IO a -> TripleMonad a +performReader = lift + +performIO :: IO a -> TripleMonad a +performIO = lift . lift + +readUserName :: TripleMonad String +readUserName = do + (maybeOldUser, _, _) <- performReader ask + case maybeOldUser of + Just str -> return str + Nothing -> do + input <- performIO getLine + if length input > 5 + then return (Just input) + else return Nothing +``` + +## Other Way for Transformers + +As the other way around, there are some typeclasses which allow you to make certain assumptions about the monad stack below inside transformers. For instance, you often don’t care what the exact stack is, but you just need IO to exist somewhere in the layers because you want to do IO actions. For this purpose, there is typeclass `MonadIO` (also from [transformers](https://hackage.haskell.org/package/transformers) package). + +```haskell +class (Monad m) => MonadIO m where + liftIO :: IO a -> m a +``` + +If you create your own monad transformer, you can/should make it instance of `MonadIO` in order to allow anyone to use it in generic way to perform IO actions using standard `liftIO` function. + +```haskell +type TripleMonad a = MaybeT (ReaderT Env IO) a + +performReader :: ReaderT Env IO a -> TripleMonad a +performReader = lift + +performIO :: IO a -> TripleMonad a +performIO = lift . lift + +instance MonadIO (TripleMonad a) where + liftIO = performIO + +-- This generic function can be now used with our TripleMonad +debugFunc :: (MonadIO m) => String -> m a +debugFunc input = do + liftIO $ putStrLn "Interpreting Input: " ++ input + liftIO $ putStrLn "Cool huh?!" +``` + +## Example from DS Wizard + +We can show some real use case from larger Haskell application - [Data Stewardship Wizard (server)](https://github.com/ds-wizard/dsw-server). Monad transformers are used, for instance, to have a nice and modular application context: + +```haskell +module Model.Context.AppContext where + +import Control.Applicative (Applicative) +import Control.Monad.IO.Class (MonadIO) +import Control.Monad.Logger (LoggingT, MonadLogger) +import Control.Monad.Reader (MonadReader, ReaderT) +import qualified Data.UUID as U +import Database.Persist.MongoDB (ConnectionPool) +import Network.AMQP (Channel) +import Network.HTTP.Client (Manager) + +import Api.Resource.User.UserDTO +import Model.Config.AppConfig +import Model.Config.BuildInfoConfig + +data AppContext = AppContext + { _appContextAppConfig :: AppConfig + , _appContextBuildInfoConfig :: BuildInfoConfig + , _appContextPool :: ConnectionPool + , _appContextMsgChannel :: Maybe Channel + , _appContextHttpClientManager :: Manager + , _appContextTraceUuid :: U.UUID + , _appContextCurrentUser :: Maybe UserDTO + } + +newtype AppContextM a = AppContextM + { runAppContextM :: ReaderT AppContext (LoggingT IO) a + } deriving (Applicative, Functor, Monad, MonadIO, MonadReader AppContext, MonadLogger) +``` + +Thanks to its default behaviour, instances of typeclasses can be easily derived (notice mainly `MonadIO`). This `AppContextM` is used across whole web application for every endpoint or service and carries all necessary resources and information including database connection, configurations, logged user, message queue connection, and others. The best thing is that when needed, it is easy to add something new to the `AppContext` type (e.g. monitoring service connection) and it won't break anything! + +Now see that you can use `IO`, `MonadReader`, and `MonadLogger` easily in the service: + +```haskell + +module Service.User.UserService where + +import Control.Lens ((&), (.~), (^.)) +import Control.Monad.Reader (asks, liftIO) +import Crypto.PasswordStore +import Data.ByteString.Char8 as BS +import Data.Maybe (fromMaybe) +import Data.Time +import qualified Data.UUID as U + +import Api.Resource.ActionKey.ActionKeyDTO +import Api.Resource.User.UserChangeDTO +import Api.Resource.User.UserCreateDTO +import Api.Resource.User.UserDTO +import Api.Resource.User.UserPasswordDTO +import Api.Resource.User.UserProfileChangeDTO +import Api.Resource.User.UserStateDTO +import Database.DAO.User.UserDAO +import LensesConfig +import Localization +import Messaging.Out.Topic.UserTopic +import Model.ActionKey.ActionKey +import Model.Config.AppConfig +import Model.Context.AppContext +import Model.Error.Error +import Model.Error.ErrorHelpers +import Model.User.User +import Service.ActionKey.ActionKeyService +import Service.Common +import Service.Mail.Mailer +import Service.User.UserMapper +import Service.User.UserValidation +import Util.Uuid + +getUsers :: AppContextM (Either AppError [UserDTO]) +getUsers = heFindUsers $ \users -> return . Right $ toDTO <$> users + +createUserByAdmin :: UserCreateDTO -> AppContextM (Either AppError UserDTO) +createUserByAdmin reqDto = do + uUuid <- liftIO generateUuid + createUserByAdminWithUuid reqDto uUuid + +createUserByAdminWithUuid :: UserCreateDTO -> U.UUID -> AppContextM (Either AppError UserDTO) +createUserByAdminWithUuid reqDto uUuid = do + uPasswordHash <- generatePasswordHash (reqDto ^. password) + dswConfig <- asks _appContextAppConfig + let uRole = fromMaybe (dswConfig ^. roles . defaultRole) (reqDto ^. role) + let uPermissions = getPermissionForRole dswConfig uRole + createUser reqDto uUuid uPasswordHash uRole uPermissions + +registrateUser :: UserCreateDTO -> AppContextM (Either AppError UserDTO) +registrateUser reqDto = + heCheckIfRegistrationIsEnabled $ do + uUuid <- liftIO generateUuid + uPasswordHash <- generatePasswordHash (reqDto ^. password) + dswConfig <- asks _appContextAppConfig + let uRole = dswConfig ^. roles . defaultRole + let uPermissions = getPermissionForRole dswConfig uRole + createUser reqDto uUuid uPasswordHash uRole uPermissions + +createUser :: UserCreateDTO -> U.UUID -> String -> Role -> [Permission] -> AppContextM (Either AppError UserDTO) +createUser reqDto uUuid uPasswordHash uRole uPermissions = + heValidateUserEmailUniqueness (reqDto ^. email) $ do + now <- liftIO getCurrentTime + let user = fromUserCreateDTO reqDto uUuid uPasswordHash uRole uPermissions now now + insertUser user + heCreateActionKey uUuid RegistrationActionKey $ \actionKey -> do + publishToUserCreatedTopic user + emailResult <- sendRegistrationConfirmationMail (toDTO user) (actionKey ^. hash) + case emailResult of + Left errMessage -> return . Left $ GeneralServerError _ERROR_SERVICE_USER__ACTIVATION_EMAIL_NOT_SENT + _ -> do + sendAnalyticsEmailIfEnabled user + return . Right $ toDTO user + where + sendAnalyticsEmailIfEnabled user = do + dswConfig <- asks _appContextAppConfig + if dswConfig ^. analytics . enabled + then sendRegistrationCreatedAnalyticsMail (toDTO user) + else return $ Right () + +getUserById :: String -> AppContextM (Either AppError UserDTO) +getUserById userUuid = heFindUserById userUuid $ \user -> return . Right $ toDTO user + +modifyUser :: String -> UserChangeDTO -> AppContextM (Either AppError UserDTO) +modifyUser userUuid reqDto = + heFindUserById userUuid $ \user -> + heValidateUserChangedEmailUniqueness (reqDto ^. email) (user ^. email) $ do + dswConfig <- asks _appContextAppConfig + updatedUser <- updateUserTimestamp $ fromUserChangeDTO reqDto user (getPermissions dswConfig reqDto user) + updateUserById updatedUser + return . Right . toDTO $ updatedUser + where + getPermissions dswConfig reqDto oldUser = + if (reqDto ^. role) /= (oldUser ^. role) + then getPermissionForRole dswConfig (reqDto ^. role) + else oldUser ^. permissions + +modifyProfile :: String -> UserProfileChangeDTO -> AppContextM (Either AppError UserDTO) +modifyProfile userUuid reqDto = + heFindUserById userUuid $ \user -> + heValidateUserChangedEmailUniqueness (reqDto ^. email) (user ^. email) $ do + updatedUser <- updateUserTimestamp $ fromUserProfileChangeDTO reqDto user + updateUserById updatedUser + return . Right . toDTO $ updatedUser + +changeUserPasswordByAdmin :: String -> UserPasswordDTO -> AppContextM (Maybe AppError) +changeUserPasswordByAdmin userUuid reqDto = + hmFindUserById userUuid $ \user -> do + passwordHash <- generatePasswordHash (reqDto ^. password) + now <- liftIO getCurrentTime + updateUserPasswordById userUuid passwordHash now + return Nothing + +changeCurrentUserPassword :: String -> UserPasswordDTO -> AppContextM (Maybe AppError) +changeCurrentUserPassword userUuid reqDto = + hmFindUserById userUuid $ \user -> do + passwordHash <- generatePasswordHash (reqDto ^. password) + now <- liftIO getCurrentTime + updateUserPasswordById userUuid passwordHash now + return Nothing + +changeUserPasswordByHash :: String -> Maybe String -> UserPasswordDTO -> AppContextM (Maybe AppError) +changeUserPasswordByHash userUuid maybeHash userPasswordDto = + validateHash maybeHash $ \akHash -> + hmFindUserById userUuid $ \user -> + hmGetActionKeyByHash akHash $ \actionKey -> do + passwordHash <- generatePasswordHash (userPasswordDto ^. password) + now <- liftIO getCurrentTime + updateUserPasswordById userUuid passwordHash now + deleteActionKey (actionKey ^. hash) + return Nothing + where + validateHash maybeHash callback = + case maybeHash of + Just akHash -> callback akHash + Nothing -> + return . Just . createErrorWithErrorMessage $ _ERROR_SERVICE_USER__REQUIRED_ADMIN_ROLE_OR_HASH_IN_QUERY_PARAMS + +resetUserPassword :: ActionKeyDTO -> AppContextM (Maybe AppError) +resetUserPassword reqDto = + hmFindUserByEmail (reqDto ^. email) $ \user -> + hmCreateActionKey (user ^. uuid) ForgottenPasswordActionKey $ \actionKey -> do + emailResult <- sendResetPasswordMail (toDTO user) (actionKey ^. hash) + case emailResult of + Left errMessage -> return . Just $ GeneralServerError _ERROR_SERVICE_USER__RECOVERY_EMAIL_NOT_SENT + _ -> return Nothing + +changeUserState :: String -> Maybe String -> UserStateDTO -> AppContextM (Maybe AppError) +changeUserState userUuid maybeHash userStateDto = + validateHash maybeHash $ \akHash -> + hmFindUserById userUuid $ \user -> + hmGetActionKeyByHash akHash $ \actionKey -> do + updatedUser <- updateUserTimestamp $ user & active .~ (userStateDto ^. active) + updateUserById updatedUser + deleteActionKey (actionKey ^. hash) + where + validateHash maybeHash callback = + case maybeHash of + Just akHash -> callback akHash + Nothing -> return . Just . createErrorWithErrorMessage $ _ERROR_SERVICE_USER__REQUIRED_HASH_IN_QUERY_PARAMS + +deleteUser :: String -> AppContextM (Maybe AppError) +deleteUser userUuid = + hmFindUserById userUuid $ \user -> do + deleteUserById userUuid + return Nothing + +-- -------------------------------- +-- PRIVATE +-- -------------------------------- +getPermissionForRole :: AppConfig -> Role -> [Permission] +getPermissionForRole config role = + case role of + "ADMIN" -> config ^. roles ^. admin + "DATASTEWARD" -> config ^. roles ^. dataSteward + "RESEARCHER" -> config ^. roles ^. researcher + _ -> [] + +generatePasswordHash :: String -> AppContextM String +generatePasswordHash password = liftIO $ BS.unpack <$> makePassword (BS.pack password) 17 + +updateUserTimestamp :: User -> AppContextM User +updateUserTimestamp user = do + now <- liftIO getCurrentTime + return $ user & updatedAt .~ Just now + +heCheckIfRegistrationIsEnabled = heCheckIfFeatureIsEnabled "Registration" (general . registrationEnabled) +``` + +## References + +* [Real World Haskell - Monad Transformers](http://book.realworldhaskell.org/read/monad-transformers.html) +* [Wikibooks - Haskell/Monad Transformers](https://en.wikibooks.org/wiki/Haskell/Monad_transformers) +* [Wikipedia - Monad Transformer](https://en.wikipedia.org/wiki/Monad_transformer) +* [Monday Morning Haskell - Monads 6](https://mmhaskell.com/monads-6) = source of most of the examples +* [A Gentle Introduction to Monad Transformers](https://github.com/kqr/gists/blob/master/articles/gentle-introduction-monad-transformers.md) +* [A Gentle Introduction to Monad Transformers](https://github.com/kqr/gists/blob/master/articles/gentle-introduction-monad-transformers.md) +* [Monad Transformers aren’t hard!](https://medium.com/@alexander.zaidel/monad-transformers-arent-hard-23387c7ef4a6) +* [Haskell Wiki - Monad Transformers Explained](https://wiki.haskell.org/Monad_Transformers_Explained) + diff --git a/tutorials/98_template-haskell.md b/tutorials/98_template-haskell.md new file mode 100644 index 0000000..7fa44fd --- /dev/null +++ b/tutorials/98_template-haskell.md @@ -0,0 +1,154 @@ +# Template Haskell + +Template Haskell is an experimental language extension to the Haskell programming language implemented in the Glasgow Haskell Compiler (version 6 and later). In early incarnations it was also known as Template Meta-Haskell. It allows **compile-time metaprogramming** and **generative programming** by means of manipulating abstract syntax trees and 'splicing' results back into a program. The abstract syntax is represented using ordinary Haskell data types and the manipulations are performed using ordinary Haskell functions. + +## Why Bother? + +With Template Haskell (TH) you can write code that generates code (i.e. metaprogramming). Sometimes it is considered as bad and result of failing to address some problem in a normal programming way. On the other hand, in some situations it can really simplify your work when you need to really generate amount of similar and complex code from simpler specification. There are some typical use cases of Template Haskell: + +* **Deriving of type class instances** - You know keyword `deriving` and we said that you can use it for basic typeclasses such as `Show`, `Read`, or `Ord`. Well, there are ways how to derive also othes. One is using [GHC.Generics](https://wiki.haskell.org/GHC.Generics) but that might slow down the compilation significantly. The other is TH and that way is used for example by [aeson](http://hackage.haskell.org/package/aeson) library when deriving instances for transformation from and to JSON. +* **Domain Specific Languages (DSLs)** - DSLs are trendy, nice, and cool way how to code things without actually writing the code itself but using different syntax in special quotes. They are integrated into systems built in Haskell. Examples of such DLSs are the language for model declaration used in [persistent](http://hackage.haskell.org/package/persistent), and various other mini-languages used in the [yesod](http://hackage.haskell.org/package/yesod) web framework that we have already shown. +* **Compile-time construction of values of refined types** - It simply turns invalid inputs into compilation failures. +* **Compile-time loading and processing of data from external files** - This is very useful sometimes to avoid loading resources on the beginning of every run of the application. Even though it involves running IO during compilation, it’s a relatively innocent use case of that dangerous feature. + +You should always reconsider using Template Haskell if there is no easier way because it is considered as dark magic (with many black boxes), might slow down the compilation and can introduce hard-to-debug problems. + +## Q Monad and Splicing + +The core of TH is the `Q` monad (short for “quotation”) that hosts all functions provided by TH: + +* Generating new unique names that cannot be captured. +* Retrieving information about a thing by its name. Usually we want to know about functions and types, but there are also ways to learn about a module, get collection of instances of a particular type class, etc. +* Putting and getting some custom state that is then shared by all TH code in the same module. +* Running IO during compilation, so we can e.g. read something from a file. + +Everything needed for basic TH is in [template-haskell](http://hackage.haskell.org/package/template-haskell) package, including the definition of the `Q` monad and other types and typeclasses that we will mention afterwards. + +```haskell +newtype Q a = Q { unQ :: forall m. Quasi m => m a } + +runQ :: Quasi m => Q a -> m a +runQ (Q m) = m + +instance Monad Q where + Q m >>= k = Q (m >>= \x -> unQ (k x)) + (>>) = (*>) + fail = Fail.fail + +instance Fail.MonadFail Q where + fail s = report True s >> Q (Fail.fail "Q monad failure") + +instance Functor Q where + fmap f (Q x) = Q (fmap f x) + +instance Applicative Q where + pure x = Q (pure x) + Q f <*> Q x = Q (f <*> x) + Q m *> Q n = Q (m *> n) +``` + +What is the `Q a` good for? - To use `a` in a Haskell program somehow. It can be anything, but we want to insert something into the Haskell code and it can be one of the following: + +* **Declaration** ([Dec](https://hackage.haskell.org/package/template-haskell/docs/Language-Haskell-TH.html#t:Dec)) = top-level things like function and data type definitions +* **Expression** ([Exp](https://hackage.haskell.org/package/template-haskell/docs/Language-Haskell-TH.html#t:Exp)) = some expression such as `if` statement, `5 + 3`, or literal `"Hello"` +* **Typed expression** ([TExp](https://hackage.haskell.org/package/template-haskell/docs/Language-Haskell-TH.html#t:TExp)) = identical to **Expression** but also defines a type of the expression "inside" +* **Type** ([Type](https://hackage.haskell.org/package/template-haskell/docs/Language-Haskell-TH.html#t:Type)) = type of any kind, for example, `Integer`, `Maybe`, `Int -> Double`, or `Either String`, including type constraints and wildcards such as `Num a => a` +* **Pattern** ([Pat](https://hackage.haskell.org/package/template-haskell/docs/Language-Haskell-TH.html#t:Pat)) = pattern for matching in function, or `case`, `where`, and `let in` + +Using those you can construct on meta-level anything that is possible to write in Haskell as a code (including Template Haskell). For example, this is how you can write in TH Haskell lambda expression `\x -> x + 1`: + +```haskell +lambdaXplus1 :: Q Exp +lambdaXplus1 = do + x <- newName "x" -- unique variable name + return $ LamE -- lambda expression + [VarP x] -- pattern matching on 'x' + (InfixE (Just (VarE x)) (VarE '(+)) (Just (LitE (IntegerL 1)))) +``` + +Such TH expression can be then used in Haskell: + +``` +GHCi> :set -XTemplateHaskell +GHCi> $(lambdaXplus1) 3 +4 +GHCi> $lambdaXplus1 3 +4 +GHCi> let f = (* 2) . $myFunc +GHCi> f 10 +22 +``` + +This is called **splicing** and the expression immediately following the `$` is called **splice**. If splice is parametric, you have to use brackets (e.g. `$(mySplice arg1 arg2) 7`), otherwise you may omit them as we did in the example above. Splice is ten evaluated to its Haskell form. + +A splice can be in four places in the code: + +* expression +* pattern +* type +* top-level declaration + +The top-level declaration splices can omit `$` since there is no ambiguity (like with the `($)` operator). Well known use of this is with lenses. + +```haskell +makeLens ''MyType +-- is the same as: +$(makeLens ''MyType) +``` + +## All the Quotations + +Doing the meta-programming in the way as shown above is not very handy and may lead to uncompilable code. Luckily, there are quotes (different for various types of code that can be generated): + +* **Declaration** = `[d| ... |]` of type `Q [Dec]` +* **Expression** = `[e| ... |]` of type `Q Exp` +* **Typed expression** = `[|| ... ||]` of type `Q (TExp a)` +* **Type** = `[t| ... |]` of type `Q Type` +* **Pattern** = `[p| ... |]` of type `Q Pat` + +In this way, the `Q`'d expression above can be rewritten easily as: + +```haskell +lambdaXplus1 :: Q Exp +lambdaXplus1 = [| \x -> x + 1 |] + +lambdasXplus2 :: Q Exp +lambdasXplus2 = [| $lambdaXplus1 . $lambdaXplus1 | +``` + +If you want to work with typed expression so the compiler can ensure that the phantom type always corresponds to what is inside, you must state the specific type: + +```haskell +lambdaXplus1 :: Q (TExp (Integer -> Integer)) +lambdaXplus1 = [|| \x -> x + 1 ||] +``` + +If you want to use typed expression in other expression you must do doubled splicing `$$`. Normal splices cannot be used in quotations for typed expressions and vice versa. Why? Of course, you must know the type in typed. For the other way, you may use `unType :: TExp a -> Exp` that gets rid of the type. + +## Quasi and runQ + +If you want to play with `Q` monad in GHCi (and apps as well) you might need `runQ` of type `Quasi m => Q a -> m a`. It is because you want to work in `IO` monad and `IO` monad is one of instances of the `Quasi` typeclass. `Quasi` is the type class for monads that provide all the capabilities for meta-programming we have mentioned in the beginning when we introduced `Q`. + +``` +GHCi> runQ [e| Just x |] +AppE (ConE GHC.Base.Just) (UnboundVarE x) +GHCi> runQ [p| Just x |] +ConP GHC.Base.Just [VarP x_0] +GHCi> runQ [| Just x |] +AppE (ConE GHC.Base.Just) (UnboundVarE x) +``` + +## Possibilities and Limitations + +There are many more options to do with Template Haskell especially when it comes to names (like with `makeLens ''MyType` or defining variables). You can, for example, then also reifying things using `reify :: Name -> Q Info`, `reifyInstances :: Name -> [Type] -> Q [InstanceDec]`, and others. But there are also some limitiations that you might encounter and have to deal with, for example, inside a splice one can only use functions that are already compiled and sometimes order of definition matters in TH (which is not the case for Haskell). + +## References + +* [Haskell Wiki - A practical Template Haskell Tutorial](https://wiki.haskell.org/A_practical_Template_Haskell_Tutorial) +* [Template Haskell tutorial (Mark Karpov)](https://markkarpov.com/tutorial/th.html) +* [Wikipedia - Template Haskell](https://en.wikipedia.org/wiki/Template_Haskell) +* [Template Haskell Is Not Scary](https://www.parsonsmatt.org/2015/11/15/template_haskell.html) +* [24 Days of GHC Extensions: Template Haskell](https://ocharles.org.uk/guest-posts/2014-12-22-template-haskell.html) +* [Syntax of Template Haskell and Quasiquotes](https://riptutorial.com/haskell/example/18471/syntax-of-template-haskell-and-quasiquotes) +* [Intro to Template Haskell](https://typeclasses.com/news/2018-10-intro-template-haskell) +* [Template Haskell 101](https://www.schoolofhaskell.com/user/marcin/template-haskell-101) diff --git a/tutorials/12_exts-deptypes.md b/tutorials/99_exts-deptypes.md similarity index 100% rename from tutorials/12_exts-deptypes.md rename to tutorials/99_exts-deptypes.md diff --git a/tutorials/images/tea.png b/tutorials/images/tea.png new file mode 100644 index 0000000..4b58d5b Binary files /dev/null and b/tutorials/images/tea.png differ