Verified Commit e00cd8e6 authored by Allele Dev's avatar Allele Dev

[59/code] add: code examples, fix post 59

Makes several corrections to post 59, and adds example code with tests
to ensure the post content is correct. In particular:

* links to code early in the post
* adds necessary imports early in the post
* removes `Alternative` instance for `Xor`
* rename `f` and `f2` to more descriptive names
    * avoids name-shadowing later
* fixes the implementation of most Validation helper functions
    * previously returned: `[Validation NumberError a]`
    *              needed: `Validation [NumberError] a`
* corrected the subsequent examples
* fixed one erroneous example
* spelling: let's -> lets
parent 31f38fc0
This diff is collapsed.
import Distribution.Simple
main = defaultMain
name: smol-types
version: 0.1.0.0
synopsis: Initial project template from stack
description: Please see README.md
license: GPL-3
license-file: LICENSE
author: Allele Dev
maintainer: allele.dev@gmail.com
copyright: Copyright (C) 2016 Allele Dev
category: Web
build-type: Simple
extra-source-files: README.md
cabal-version: >=1.10
library
hs-source-dirs: src
exposed-modules: Color
build-depends: base >= 4.7 && < 5
ghc-options: -Wall
default-language: Haskell2010
module Color where
data Color = Red | Green | Blue | Cyan
colorToInt4 :: Color -> Int
colorToInt4 c =
case c of
Red -> 0
Green -> 1
Blue -> 2
f = colorToInt4 Cyan
# This file was automatically generated by 'stack init'
#
# Some commonly used options have been documented as comments in this file.
# For advanced use and comprehensive documentation of the format, please see:
# http://docs.haskellstack.org/en/stable/yaml_configuration/
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
# A snapshot resolver dictates the compiler version and the set of packages
# to be used for project dependencies. For example:
#
# resolver: lts-3.5
# resolver: nightly-2015-09-21
# resolver: ghc-7.10.2
# resolver: ghcjs-0.1.0_ghc-7.10.2
# resolver:
# name: custom-snapshot
# location: "./custom-snapshot.yaml"
resolver: lts-7.14
# User packages to be built.
# Various formats can be used as shown in the example below.
#
# packages:
# - some-directory
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
# - location:
# git: https://github.com/commercialhaskell/stack.git
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a
# extra-dep: true
# subdirs:
# - auto-update
# - wai
#
# A package marked 'extra-dep: true' will only be built if demanded by a
# non-dependency (i.e. a user package), and its test suites and benchmarks
# will not be run. This is useful for tweaking upstream packages.
packages:
- '.'
# Dependency packages to be pulled from upstream that are not in the resolver
# (e.g., acme-missiles-0.3)
extra-deps: []
# Override default flag values for local packages and extra-deps
flags: {}
# Extra package databases containing global packages
extra-package-dbs: []
# Control whether we use the GHC we find on the path
# system-ghc: true
#
# Require a specific version of stack, using version ranges
# require-stack-version: -any # Default
# require-stack-version: ">=1.3"
#
# Override the architecture used by stack, especially useful on Windows
# arch: i386
# arch: x86_64
#
# Extra directories used by stack for building
# extra-include-dirs: [/path/to/dir]
# extra-lib-dirs: [/path/to/dir]
#
# Allow a newer minor version of GHC than the snapshot specifies
# compiler-check: newer-minor
\ No newline at end of file
# haskell
.cabal-sandbox
.hsenv
.stack-work
cabal-project.local
cabal.sandbox.config
dist
dist-newstyle
stack.yaml
# emacs
TAGS
tags
codex.tags
module Main where
import System.Environment
import Test.DocTest
main :: IO ()
main = do
args <- getArgs
doctest (["src"] ++ args)
This diff is collapsed.
# Applicatives and Alternatives
This is the code for the blog
post,
[Applicatives and Alternatives](https://queertypes.com/posts/59-applicatives-alternatives.html).
The example code is fully interactive. You'll
need [stack](https://docs.haskellstack.org/en/stable/README/) to play
with it.
* To install the GHC compiler, if it's missing: `stack setup`
* To build the code: `stack build`
* To play with it in the REPL: `stack ghci`
* To run tests: `stack exec doctests`
import Distribution.Simple
main = defaultMain
name: app-alt
version: 0.1.0.0
synopsis: Example code for post 59 of queertypes.com
description: Please see README.md
license: GPL-3
license-file: LICENSE
author: Allele Dev
maintainer: allele.dev@gmail.com
copyright: Copyright (C) 2017 Allele Dev
category: Post
build-type: Simple
extra-source-files:
cabal-version: >=1.20
library
hs-source-dirs: src
exposed-modules: FiftyNine
build-depends: base >= 4.7 && < 5
ghc-options: -Wall
default-language: Haskell2010
executable doctests
main-is: Doctests.hs
build-depends: base
, doctest >= 0.11
, QuickCheck >= 2.8
ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
default-language: Haskell2010
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
module FiftyNine (
Option(..),
Xor(..),
Validation(..),
NumberError(..),
validateNumber,
validateNumberV
) where
-------------------------------------------------------------------------------
-- Imports
-------------------------------------------------------------------------------
import Prelude
import Control.Applicative
import Data.Monoid
-- ============================================================================
-- Option/Maybe Type
-- ============================================================================
data Option a
= Some a
| None
deriving Show
instance Functor Option where
fmap f (Some a) = Some (f a)
fmap _ None = None
instance Applicative Option where
pure = Some
Some f <*> Some a = Some (f a)
None <*> Some _ = None
Some _ <*> None = None
None <*> None = None
instance Alternative Option where
empty = None
Some a <|> Some _ = Some a
Some a <|> None = Some a
None <|> Some a = Some a
None <|> None = None
-------------------------------------------------------------------------------
-- Option Examples
-------------------------------------------------------------------------------
add3 :: Num a => a -> a -> a -> a
add3 a b c = a + b + c
add3Opt :: Num a => Option a -> Option a -> Option a -> Option a
add3Opt a b c =
case a of
None -> None
(Some a') -> case b of
None -> None
(Some b') -> case c of
None -> None
(Some c') -> Some (add3 a' b' c')
isNone :: Option a -> Bool
isNone None = True
isNone _ = False
-- dangerous
getOption :: Option a -> a
getOption (Some a) = a
getOption None = error "oh no crash"
f2 :: Num a => Option a -> Option a -> Option a -> Option a
f2 a b c =
if isNone a
then None
else if isNone b
then None
else if isNone c
then None
else Some (add3 (getOption a) (getOption b) (getOption c))
f3 :: (Applicative f, Num b) => f b -> f b -> f b -> f b
f3 a b c = add3 <$> a <*> b <*> c
-------------------------------------------------------------------------------
-- Option Doctests
-------------------------------------------------------------------------------
-- | Option examples
-- >>> (+) <$> Some 1 <*> Some 2
-- Some 3
-- >>> (+) `fmap` Some 1 <*> Some 2
-- Some 3
-- >>> (+) <$> Some 1 <*> None
-- None
-- >>> add3 a b c = a + b + c
-- >>> :t add3
-- add3 :: Num a => a -> a -> a -> a
-- >>> add3 <$> Some 1 <*> Some 2 <*> Some 3
-- Some 6
-- >>> add3 <$> Some 1 <*> None <*> Some 3
-- None
-- >>> None <|> Some 1
-- Some 1
-- >>> None <|> None
-- None
-- >>> None <|> None <|> Some 3
-- Some 3
-- ============================================================================
-- Xor/Either Types
-- ============================================================================
data Xor a b
= XLeft a
| XRight b
deriving Show
instance Functor (Xor a) where
fmap f (XRight a) = XRight (f a)
fmap _ (XLeft a) = XLeft a
instance Applicative (Xor a) where
pure = XRight
XRight f <*> XRight a = XRight (f a)
XRight _ <*> XLeft a = XLeft a
XLeft a <*> XRight _ = XLeft a
XLeft a <*> XLeft _ = XLeft a -- choose the first error to short-circuit
-------------------------------------------------------------------------------
-- Xor/Either Examples
-------------------------------------------------------------------------------
data NumberError
= NumberTooBig
| NumberTooSmall
| NumberNotAllowed
deriving Show
validateNumber :: Int -> Xor NumberError Int
validateNumber x
| x > 500 = XLeft NumberTooBig
| x < 30 = XLeft NumberTooSmall
| x `elem` [42, 69, 420] = XLeft NumberNotAllowed
| otherwise = XRight x
-------------------------------------------------------------------------------
-- Xor/Either Doctests
-------------------------------------------------------------------------------
-- | Xor worked examples
--
-- >>> validateNumber 32
-- XRight 32
-- >>> validateNumber 69
-- XLeft NumberNotAllowed
-- >>> validateNumber 501
-- XLeft NumberTooBig
-- >>> validateNumber 29
-- XLeft NumberTooSmall
-- >>> (+) <$> validateNumber 31 <*> validateNumber 33
-- XRight 64
-- >>> (+) <$> validateNumber 31 <*> validateNumber 42
-- XLeft NumberNotAllowed
-- ============================================================================
-- Validation Types
-- ============================================================================
data Validation a b
= Failure a
| Success b
deriving Show
instance Functor (Validation a) where
fmap f (Success a) = Success (f a)
fmap _ (Failure a) = Failure a
-- accumulating errors
instance Monoid a => Applicative (Validation a) where
pure = Success
Success f <*> Success a = Success (f a)
Failure a <*> Success _ = Failure a
Success _ <*> Failure a = Failure a
Failure a <*> Failure b = Failure (a <> b)
instance Monoid a => Alternative (Validation a) where
empty = Failure mempty
Success a <|> Success _ = Success a
Success a <|> Failure _ = Success a
Failure _ <|> Success a = Success a
Failure _ <|> Failure a = Failure a
-------------------------------------------------------------------------------
-- Validation Examples
-------------------------------------------------------------------------------
numberTooSmall :: Validation [NumberError] a
numberTooSmall = Failure [NumberTooSmall]
numberTooBig :: Validation [NumberError] a
numberTooBig = Failure [NumberTooBig]
numberNotAllowed :: Validation [NumberError] a
numberNotAllowed = Failure [NumberNotAllowed]
validateNumberV :: Int -> Validation [NumberError] Int
validateNumberV x
| x > 500 = numberTooBig
| x < 30 = numberTooSmall
| x `elem` [42, 69, 420] = numberNotAllowed
| otherwise = Success x
-- | Validation doctests
--
-- >>> validateNumberV 32
-- Success 32
-- >>> validateNumberV 69
-- Failure [NumberNotAllowed]
-- >>> add3V a b c = a + b + c
-- >>> add3V <$> validateNumberV 32 <*> validateNumberV 33 <*> validateNumberV 34
-- Success 99
-- >>> add3V <$> validateNumberV 42 <*> validateNumberV 69 <*> validateNumberV 420
-- Failure [NumberNotAllowed,NumberNotAllowed,NumberNotAllowed]
-- >>> add3V <$> validateNumberV 42 <*> validateNumberV 10 <*> validateNumberV 50
-- Failure [NumberNotAllowed,NumberTooSmall]
-- >>> :{
-- (+) <$> validateNumberV 42 <*> validateNumberV 10
-- <|> (+) <$> validateNumberV 41 <*> validateNumberV 31
-- :}
-- Success 72
......@@ -3,6 +3,12 @@ title: Applicatives and Alternatives
date: April 21, 2017
---
## Updates/Edits
* May 9, 2017: FILL ME IN
## Introduction
This post is a tutorial on applicative functors (applicatives) and
alternatives - what they are, how they're used, and how to build an
intuition for them.
......@@ -10,9 +16,18 @@ intuition for them.
It's going to be extremely practical. It's going to be all
Haskell. We'll build stuff along the way so that it makes more sense.
The following imports are assumed to be in scope for this post:
```haskell
import Control.Applicative
import Data.Monoid
```
All code used in this post is available here: FILL ME IN.
Let's start with some informal definitions.
An applicative is an abstraction that let's us express function
An applicative is an abstraction that lets us express function
composition in a context. Applicative composition only succeeds if
each independent component in the computation succeeds. Rules for what
success looks like vary from context to context. Its interface looks
......@@ -48,7 +63,6 @@ class Applicative f => Alternative (f :: * -> *) where
alternatives. `<|>` is our composition operator, for chaining together
alternatives.
With those definitions out of the way -
Let's build our first applicative! To do so, we'll look at the
......@@ -106,7 +120,6 @@ I'll provide some examples next. A few notes:
* `<$>` is `fmap`, or, the means to apply a pure function in a context
* `:{` is used in the Haskell REPL to indicate multi-line input
* `:}` terminates multi-line input
* These examples require `import Control.Applicative`
Here's how what we've written looks like in use:
......@@ -122,7 +135,7 @@ None
> add3 a b c = a + b + c
> :t add3
add3 :: Number a => a -> a -> a -> a
add3 :: Num a => a -> a -> a -> a
> add3 <$> Some 1 <*> Some 2 <*> Some 3
Some 6
......@@ -158,8 +171,8 @@ need, like with `add3`. This is much more convenient than pattern
matching on the spot every time, like:
```haskell
f :: Number a => Option a -> Option a -> Option a -> Option a
f a b c =
add3OptLengthy :: Num a => Option a -> Option a -> Option a -> Option a
add3OptLengthy a b c =
case a of
None -> None
(Some a') -> case b of
......@@ -181,8 +194,8 @@ getOption :: Option a -> a
getOption (Some a) = a
getOption None = error "oh no crash"
f2 :: Number a => Option a -> Option a -> Option a -> Option a
f2 a b c =
add3OptUnsafe :: Num a => Option a -> Option a -> Option a -> Option a
add3OptUnsafe a b c =
if isNone a
then None
else if isNone b
......@@ -198,7 +211,7 @@ us so we don't have to repeat ourselves. All of the above to say:
```haskell
> f3 a b c = add3 <$> a <*> b <*> c
> :t f3
Number a => Option a -> Option a -> Option a -> Option a
Num a => Option a -> Option a -> Option a -> Option a
```
Let's look at a new context type now: `Xor`
......@@ -236,16 +249,12 @@ instance Applicative (Xor a) where
As before, the only way to get to a success is to have successes all
along the way.
Here's the implementation for `Alternative`:
```haskell
instance Monoid a => Alternative (Xor a) where
empty = XLeft mempty
XRight a <|> XRight _ = XRight a
XRight a <|> XLeft _ = XRight a
XLeft _ <|> XRight a = XRight a
XLeft _ <|> XLeft a = XLeft a
```
`Xor` does not admit an `Alternative` instance. This is because, there
is no reasonable definition of `Alternative`'s `empty`. If the
left-branch type `a` was a `Monoid`, then it'd be possible to define,
but it still wouldn't be very interesting. We'll elide that for now
and show a more reasonable error-capturing type in the next section
that allows for alternatives.
For the examples involving `Xor`, we'll first define a simple error
data type with a few helper functions:
......@@ -285,12 +294,6 @@ XRight 64
> (+) <$> validateNumber 31 <*> validateNumber 42
XLeft NumberNotAllowed
> validateNumber 31 <|> validateNumber 42
XRight 31
> validateNumber 42 <|> validateNumber 33
XRight 33
```
Next, let's look at an extension to the `Xor` type - the `Validation`
......@@ -306,7 +309,7 @@ them away. It looks fairly similar to `Xor`, even:
data Validation a b
= Failure a
| Success b
deriving Show
```
Exchange `Validation` with `Xor`, `Failure` with `XLeft`, and
......@@ -354,19 +357,19 @@ around our `NumberError` data type from before:
```haskell
numberTooSmall :: Validation NumberError a
numberTooSmall = Failure NumberTooSmall
numberTooSmall = Failure [NumberTooSmall]
numberTooBig :: Validation NumberError a
numberTooBig = Failure NumberTooBig
numberTooBig = Failure [NumberTooBig]
numberNotAllowed :: Validation NumberError a
numberNotAllowed = Failure NumberNotAllowed
numberNotAllowed :: Validation [NumberError] a
numberNotAllowed = Failure [NumberNotAllowed]
validateNumberV :: Int -> Validation [NumberError] Int
validateNumberV x
| x > 500 = [numberTooBig]
| x < 30 = [numberTooSmall]
| x `elem` [42, 69, 420] = [numberNotAllowed]
| x > 500 = numberTooBig
| x < 30 = numberTooSmall
| x `elem` [42, 69, 420] = numberNotAllowed
| otherwise = Success x
```
......@@ -377,7 +380,7 @@ And some examples:
Success 32
> validateNumberV 69
[Failure NumberNotAllowed]
Failure [NumberNotAllowed]
> add3V a b c = a + b + c
......@@ -385,16 +388,16 @@ Success 32
Success 99
> add3V <$> validateNumberV 42 <*> validateNumberV 69 <*> validateNumberV 420
[Failure NumberNotAllowed, Failure NumberNotAllowed, Failure NumberNotAllowed]
Failure [NumberNotAllowed, NumberNotAllowed, NumberNotAllowed]
> add3V <$> validateNumberV 42 <*> validateNumberV 10 <*> validateNumberV 50
[Failure NumberNotAllowed, Failure NumberTooSmall]
Failure [NumberNotAllowed, NumberTooSmall]
> :{
| (+) <$> validateNumberV 42 <*> validateNumberV 10
| <|> (+) <$> validateNumberV 41 <*> validateNumberV 10
| :}
Success 51
(+) <$> validateNumberV 42 <*> validateNumberV 10
<|> (+) <$> validateNumberV 41 <*> validateNumberV 31
:}
Success 72
```
These examples are mostly silly, mainly for showing the mechanics, but
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment