Skip to content

Instantly share code, notes, and snippets.

@bkyrlach
Last active October 25, 2020 07:56
Show Gist options
  • Save bkyrlach/b56823630c8cc049d7660a009858b0c9 to your computer and use it in GitHub Desktop.
Save bkyrlach/b56823630c8cc049d7660a009858b0c9 to your computer and use it in GitHub Desktop.
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