Last active
October 25, 2020 07:56
-
-
Save bkyrlach/b56823630c8cc049d7660a009858b0c9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Combining where | |
import Prelude hiding (Semigroup) | |
import Data.List as List | |
-- We know that lots of things can be combined together. | |
combineLists :: [a] -> [a] -> [a] | |
combineLists [] [] = [] | |
combineLists [] ys = ys | |
combineLists xs ys = head xs : combineLists (tail xs) ys | |
-- Sometimes even many ways to combine the same thing together | |
-- Like Ints | |
combineInts :: Int -> Int -> Int | |
combineInts a b = a + b | |
combineInts' :: Int -> Int -> Int | |
combineInts' a b = a * b | |
-- Or Bools | |
combineBools :: Bool -> Bool -> Bool | |
combineBools a b = a && b | |
combineBools' :: Bool -> Bool -> Bool | |
combineBools' a b = a || b | |
-- The first thing we might notice is that all of these combine functions | |
-- share a similar shape. | |
-- combine :: a -> a -> a | |
-- Haskell makes it kind of easy to see these underlying mathematical | |
-- relationships, and express them. In this case, we've discovered a | |
-- mathematical concept called `Semigroup`. | |
-- We can easily express concepts that span multiple data types using | |
-- Haskells typeclass mechanism | |
class Semigroup a where | |
append :: a -> a -> a | |
-- You can now implement this for the data types above (we'll just do Bool | |
-- for now). | |
instance Semigroup Bool where | |
append = (&&) | |
-- This lets us define combine from earlier... | |
combine :: (Semigroup a) => a -> a -> a | |
combine = append | |
-- This related to our question before. Can we combine two functions of type | |
-- `a -> b` into a single function. (Well, specifically your question was | |
-- about two functions `Char -> Bool`, but I've just generalized to `a -> b`) | |
-- The first question we might ask is, how exactly do we combine them? | |
-- Instead of making you guess, I'll suggest something I believe makes | |
-- sense, and we can explore it below. | |
-- To combine two functions of type `a -> b` we will apply each function to | |
-- the input value of type `a`, and then as long as we have a `Semigroup b` then | |
-- we can combine the two output values into one. | |
-- Note that this definition satisfies our current idea of `Semigroup`! | |
instance (Semigroup b) => Semigroup (a -> b) where | |
append f g = \x -> append (f x) (g x) | |
-- With this instance defined, we should be able to now combine any two | |
-- predicate functions. | |
isEven :: Int -> Bool | |
isEven = (== 0) . ((flip rem) 2) | |
gtTen :: Int -> Bool | |
gtTen = (> 10) | |
xs = List.filter (append isEven gtTen) [5..15] | |
-- Will work with other functions too, as long as we have our `Semigroup b` | |
-- (which below is `Semigroup Int`) | |
instance Semigroup Int where | |
append = (+) | |
inc :: Int -> Int | |
inc = (+1) | |
times2 :: Int -> Int | |
times2 = (*2) | |
ys = fmap (append inc times2) [1..10] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment