diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
 # kempe
 
+## 0.2.0.7
+
+  * Fix bug in unification
+  * Fix bug so that `and` and `or` instructions print correctly for x86 assembler
+  * Add lints for `dip(+) +` to `+ +`, (associative dip) `dup and` to `id`, etc.
+  * Add lints for `swap swap` and `1 drop` etc.
+  * Add `join` for `Maybe` and `Either`
+  * Last branch of case statement always falls through (more efficient code)
+
 ## 0.2.0.6
 
   * Add `absInt` and `chocie` functions to prelude.
diff --git a/golden/Golden.hs b/golden/Golden.hs
--- a/golden/Golden.hs
+++ b/golden/Golden.hs
@@ -26,4 +26,5 @@
     , ("test/examples/bool.kmp", "test/harness/bool.c", "test/golden/bool.out")
     , ("test/examples/const.kmp", "test/harness/const.c", "test/golden/const.out")
     , ("test/data/badCodegen.kmp", "test/harness/id.c", "test/golden/id.out")
+    , ("test/data/mod.kmp", "test/harness/mod.c", "test/golden/mod.out")
     ]
diff --git a/kempe.cabal b/kempe.cabal
--- a/kempe.cabal
+++ b/kempe.cabal
@@ -1,6 +1,6 @@
 cabal-version:   3.0
 name:            kempe
-version:         0.2.0.6
+version:         0.2.0.7
 license:         BSD-3-Clause
 license-file:    LICENSE
 copyright:       Copyright: (c) 2020-2021 Vanessa McHale
diff --git a/lib/either.kmp b/lib/either.kmp
--- a/lib/either.kmp
+++ b/lib/either.kmp
@@ -11,6 +11,14 @@
     }
 ]
 
+join : ((Either a) ((Either a) b)) -- ((Either a) b)
+     =: [
+    { case
+        | Left  -> Left
+        | Right ->
+    }
+]
+
 isLeft : ((Either a) b) -- Bool
        =: [
     { case
diff --git a/lib/maybe.kmp b/lib/maybe.kmp
--- a/lib/maybe.kmp
+++ b/lib/maybe.kmp
@@ -8,6 +8,14 @@
     }
 ]
 
+join : (Maybe (Maybe a)) -- (Maybe a)
+     =: [
+    { case
+        | Just ->
+        | Nothing -> Nothing
+    }
+]
+
 isJust : (Maybe a) -- Bool
        =: [
     { case
diff --git a/prelude/arith.kmp b/prelude/arith.kmp
--- a/prelude/arith.kmp
+++ b/prelude/arith.kmp
@@ -1,6 +1,21 @@
 import "prelude/fn.kmp"
 import "lib/maybe.kmp"
 
+; like haskell; % is rem and mod is... mod
+;
+; from here: https://hackage.haskell.org/package/ghc-prim-0.7.0/docs/src/GHC-Classes.html#modInt%23
+modInt : Int Int -- Int
+       =: [ dup2 dup2
+            0 < dip(0 >) &
+            dip(0 > dip(0 <) &)
+            || 
+            dip(dup dip(%))
+            if( dip(dip(dup) 0 !=) swap if(+, nip)
+              , drop
+              )
+          ]
+
+
 succInt : Int -- Int
         =: [ 1 + ]
 
diff --git a/src/Kempe/Asm/X86/Type.hs b/src/Kempe/Asm/X86/Type.hs
--- a/src/Kempe/Asm/X86/Type.hs
+++ b/src/Kempe/Asm/X86/Type.hs
@@ -279,8 +279,8 @@
     pretty (DivR _ r)           = i4 ("div" <+> pretty r)
     pretty Cqo{}                = i4 "cqo"
     pretty (MovACTag _ a t)     = i4 ("mov byte" <+> pretty a <> "," <+> pretty t)
-    pretty (AndRR _ r0 r1)      = i4 ("and" <+> pretty r0 <+> pretty r1)
-    pretty (OrRR _ r0 r1)       = i4 ("or" <+> pretty r0 <+> pretty r1)
+    pretty (AndRR _ r0 r1)      = i4 ("and" <+> pretty r0 <> "," <+> pretty r1)
+    pretty (OrRR _ r0 r1)       = i4 ("or" <+> pretty r0 <> "," <+> pretty r1)
     pretty (PopcountRR _ r0 r1) = i4 ("popcnt" <+> pretty r0 <> "," <+> pretty r1)
     pretty (NegR _ r)           = i4 ("neg" <+> pretty r)
     pretty (Jne _ l)            = i4 ("jne" <+> prettyLabel l)
diff --git a/src/Kempe/Check/Lint.hs b/src/Kempe/Check/Lint.hs
--- a/src/Kempe/Check/Lint.hs
+++ b/src/Kempe/Check/Lint.hs
@@ -16,22 +16,40 @@
 lintDecl ExtFnDecl{}          = Nothing
 lintDecl (FunDecl _ _ _ _ as) = lintAtoms as
 
+-- TODO: swap drop drop -> drop drop
+-- TODO: dup - -> 0
+
+-- a bunch of this is from http://joy-lang.org/papers-on-joy/the-algebra-of-joy/
 lintAtoms :: [Atom b b] -> Maybe (Warning b)
-lintAtoms []                                                = Nothing
-lintAtoms (a@(Dip l _):a'@Dip{}:_)                          = Just (DoubleDip l a a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ IntEq):_)     = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ IntNeq):_)    = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ And):_)       = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ Or):_)        = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ Xor):_)       = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ WordXor):_)   = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ IntTimes):_)  = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ IntPlus):_)   = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ WordPlus):_)  = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ WordTimes):_) = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ IntXor):_)    = Just (SwapBinary l a' a')
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin l' IntGt):_)    = Just (SwapBinary l a' (AtBuiltin l' IntLt))
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin l' IntGeq):_)   = Just (SwapBinary l a' (AtBuiltin l' IntLeq))
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin l' IntLt):_)    = Just (SwapBinary l a' (AtBuiltin l' IntGt))
-lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin l' IntLeq):_)   = Just (SwapBinary l a' (AtBuiltin l' IntGeq))
-lintAtoms (_:as)                                            = lintAtoms as
+lintAtoms []                                                            = Nothing
+lintAtoms (a@(Dip l _):a'@Dip{}:_)                                      = Just (DoubleDip l a a')
+lintAtoms (a@(IntLit l _):(AtBuiltin _ Drop):_)                         = Just (PushDrop l a)
+lintAtoms (a@(WordLit l _):(AtBuiltin _ Drop):_)                        = Just (PushDrop l a)
+lintAtoms (a@(BoolLit l _):(AtBuiltin _ Drop):_)                        = Just (PushDrop l a)
+lintAtoms (a@(Int8Lit l _):(AtBuiltin _ Drop):_)                        = Just (PushDrop l a)
+lintAtoms ((Dip l [AtBuiltin _ IntPlus]):a@(AtBuiltin _ IntPlus):_)     = Just (DipAssoc l a)
+lintAtoms ((Dip l [AtBuiltin _ IntTimes]):a@(AtBuiltin _ IntTimes):_)   = Just (DipAssoc l a)
+lintAtoms ((Dip l [AtBuiltin _ WordPlus]):a@(AtBuiltin _ WordPlus):_)   = Just (DipAssoc l a)
+lintAtoms ((Dip l [AtBuiltin _ WordTimes]):a@(AtBuiltin _ WordTimes):_) = Just (DipAssoc l a)
+lintAtoms ((Dip l [AtBuiltin _ And]):a@(AtBuiltin _ And):_)             = Just (DipAssoc l a)
+lintAtoms ((Dip l [AtBuiltin _ Or]):a@(AtBuiltin _ Or):_)               = Just (DipAssoc l a)
+lintAtoms ((Dip l [AtBuiltin _ IntEq]):a@(AtBuiltin _ IntEq):_)         = Just (DipAssoc l a)
+lintAtoms ((AtBuiltin l Swap):(AtBuiltin _ Swap):_)                     = Just (DoubleSwap l)
+lintAtoms ((AtBuiltin l Dup):a@(AtBuiltin _ And):_)                     = Just (Identity l a)
+lintAtoms ((AtBuiltin l Dup):a@(AtBuiltin _ Or):_)                      = Just (Identity l a)
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ IntEq):_)                 = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ IntNeq):_)                = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ And):_)                   = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ Or):_)                    = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ Xor):_)                   = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ WordXor):_)               = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ IntTimes):_)              = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ IntPlus):_)               = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ WordPlus):_)              = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ WordTimes):_)             = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin _ IntXor):_)                = Just (SwapBinary l a' a')
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin l' IntGt):_)                = Just (SwapBinary l a' (AtBuiltin l' IntLt))
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin l' IntGeq):_)               = Just (SwapBinary l a' (AtBuiltin l' IntLeq))
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin l' IntLt):_)                = Just (SwapBinary l a' (AtBuiltin l' IntGt))
+lintAtoms ((AtBuiltin l Swap):a'@(AtBuiltin l' IntLeq):_)               = Just (SwapBinary l a' (AtBuiltin l' IntGeq))
+lintAtoms (_:as)                                                        = lintAtoms as
diff --git a/src/Kempe/Error/Warning.hs b/src/Kempe/Error/Warning.hs
--- a/src/Kempe/Error/Warning.hs
+++ b/src/Kempe/Error/Warning.hs
@@ -13,11 +13,19 @@
 data Warning a = NameClash a (Name a)
                | DoubleDip a (Atom a a) (Atom a a)
                | SwapBinary a (Atom a a) (Atom a a)
+               | DoubleSwap a
+               | DipAssoc a (Atom a a)
+               | Identity a (Atom a a)
+               | PushDrop a (Atom a a)
 
 instance Pretty a => Pretty (Warning a) where
     pretty (NameClash l x)     = pretty l <> " '" <> pretty x <> "' is defined more than once."
     pretty (DoubleDip l a a')  = pretty l <+> pretty a <+> pretty a' <+> "could be written as a single dip()"
     pretty (SwapBinary l a a') = pretty l <+> squotes ("swap" <+> pretty a) <+> "is" <+> pretty a'
+    pretty (DoubleSwap l)      = pretty l <+> "double swap"
+    pretty (DipAssoc l a)      = pretty l <+> "dip(" <> pretty a <> ")" <+> pretty a <+> "is equivalent to" <+> pretty a <+> pretty a <+> "by associativity"
+    pretty (Identity l a)      = pretty l <+> squotes ("dup" <+> pretty a) <+> "is identity"
+    pretty (PushDrop l a)      = pretty l <+> squotes (pretty a <+> "drop") <+> "is identity"
 
 instance (Pretty a) => Show (Warning a) where
     show = show . pretty
diff --git a/src/Kempe/IR.hs b/src/Kempe/IR.hs
--- a/src/Kempe/IR.hs
+++ b/src/Kempe/IR.hs
@@ -10,6 +10,7 @@
 import           Data.List.NonEmpty         (NonEmpty (..))
 import qualified Data.List.NonEmpty         as NE
 -- strict b/c it's faster according to benchmarks
+import           Control.Monad              (zipWithM)
 import           Control.Monad.State.Strict (State, gets, modify, runState)
 import           Data.Bifunctor             (second)
 import           Data.Foldable.Ext
@@ -249,15 +250,13 @@
     let (ps, ass) = NE.unzip ls
         decSz = size' env (last is)
         in do
-            leaves <- zipWithM (mkLeaf env l) ps ass
-            let (switches, meat) = NE.unzip leaves
+            leaves <- zipWithM (mkLeaf env l) (toList ps) (NE.init ass)
+            lastLeaf <- mkLeaf env l (PatternWildcard undefined) (NE.last ass)
+            let (switches, meat) = unzip (leaves ++ [lastLeaf])
             ret <- newLabel
             let meat' = (++ [Jump ret]) . toList <$> meat
             pure $ dataPointerDec decSz : concatMap toList switches ++ concat meat' ++ [Labeled ret]
             -- TODO: why dataPointerDec decSz??
-
-zipWithM :: (Applicative m) => (a -> b -> m c) -> NonEmpty a -> NonEmpty b -> m (NonEmpty c)
-zipWithM f xs ys = sequenceA (NE.zipWith f xs ys)
 
 mkLeaf :: SizeEnv -> Bool -> Pattern (ConsAnn MonoStackType) MonoStackType -> [Atom (ConsAnn MonoStackType) MonoStackType] -> TempM ([Stmt], [Stmt])
 mkLeaf env l p as = do
diff --git a/src/Kempe/TyAssign.hs b/src/Kempe/TyAssign.hs
--- a/src/Kempe/TyAssign.hs
+++ b/src/Kempe/TyAssign.hs
@@ -96,6 +96,7 @@
 inContext um ty'@(TyVar _ (Name _ (Unique i) _)) =
     case IM.lookup i um of
         Just ty@TyVar{} -> inContext (IM.delete i um) ty -- prevent cyclic lookups
+        -- TODO: does this need a case for TyApp -> inContext?
         Just ty         -> ty
         Nothing         -> ty'
 inContext _ ty'@TyBuiltin{} = ty'
@@ -122,7 +123,6 @@
 unifyMatch um ((TyVar _ (Name _ (Unique k) _), ty@(TyNamed _ _)):tys)        = IM.insert k ty <$> unifyPrep (IM.insert k ty um) tys
 unifyMatch um ((ty@TyBuiltin{}, TyVar  _ (Name _ (Unique k) _)):tys)         = IM.insert k ty <$> unifyPrep (IM.insert k ty um) tys
 unifyMatch um ((TyVar _ (Name _ (Unique k) _), ty@(TyBuiltin _ _)):tys)      = IM.insert k ty <$> unifyPrep (IM.insert k ty um) tys
-unifyMatch um ((TyVar _ (Name _ (Unique k) _), ty@(TyVar _ _)):tys)          = IM.insert k ty <$> unifyPrep (IM.insert k ty um) tys
 unifyMatch _ ((ty@TyBuiltin{}, ty'@TyNamed{}):_)                             = Left (UnificationFailed () ty ty')
 unifyMatch _ ((ty@TyNamed{}, ty'@TyBuiltin{}):_)                             = Left (UnificationFailed () ty ty')
 unifyMatch _ ((ty@TyBuiltin{}, ty'@TyApp{}):_)                               = Left (UnificationFailed () ty ty')
@@ -130,8 +130,11 @@
 unifyMatch _ ((ty@TyApp{}, ty'@TyBuiltin{}):_)                               = Left (UnificationFailed () ty ty')
 unifyMatch um ((TyVar _ (Name _ (Unique k) _), ty@TyApp{}):tys)              = IM.insert k ty <$> unifyPrep (IM.insert k ty um) tys
 unifyMatch um ((ty@TyApp{}, TyVar  _ (Name _ (Unique k) _)):tys)             = IM.insert k ty <$> unifyPrep (IM.insert k ty um) tys
-unifyMatch um ((TyApp _ ty ty', TyApp _ ty'' ty'''):tys)                     = unifyMatch um ((ty, ty'') : (ty', ty''') : tys)
+unifyMatch um ((TyApp _ ty ty', TyApp _ ty'' ty'''):tys)                     = unifyMatch um ((ty, ty'') : (ty', ty''') : tys) -- TODO:  do we need unifyPrep here?
 unifyMatch _ ((ty@TyApp{}, ty'@TyNamed{}):_)                                 = Left (UnificationFailed () (void ty) (void ty'))
+unifyMatch um ((TyVar _ n@(Name _ (Unique k) _), ty@(TyVar _ n')):tys)
+    | n == n' = unifyMatch um tys -- a type variable is always equal to itself, don't bother inserting this!
+    | otherwise = IM.insert k ty <$> unifyPrep (IM.insert k ty um) tys
 
 unify :: [(KempeTy (), KempeTy ())] -> Either (Error ()) (IM.IntMap (KempeTy ()))
 unify = unifyPrep IM.empty
@@ -276,7 +279,7 @@
 assignAtom (AtCons _ tn) = do
     sTy <- renameStack =<< consLookup (void tn)
     pure (sTy, AtCons sTy (tn $> sTy))
-assignAtom (Dip _ as)    = do { (as', ty) <- assignAtoms as ; tyDipped <- dipify ty ; pure (tyDipped, Dip tyDipped as') }
+assignAtom (Dip _ as) = do { (as', ty) <- assignAtoms as ; tyDipped <- dipify ty ; pure (tyDipped, Dip tyDipped as') }
 assignAtom (If _ as0 as1) = do
     (as0', tys) <- assignAtoms as0
     (as1', tys') <- assignAtoms as1
diff --git a/test/Backend.hs b/test/Backend.hs
--- a/test/Backend.hs
+++ b/test/Backend.hs
@@ -26,6 +26,7 @@
         , pipelineWorks "examples/factorial.kmp"
         , pipelineWorks "test/data/mutual.kmp"
         , pipelineWorks "test/data/multiConstruct.kmp"
+        , pipelineWorks "test/data/mod.kmp"
         , irNoYeet "test/data/export.kmp"
         , irNoYeet "examples/splitmix.kmp"
         , irNoYeet "examples/factorial.kmp"
diff --git a/test/data/mod.kmp b/test/data/mod.kmp
new file mode 100644
--- /dev/null
+++ b/test/data/mod.kmp
@@ -0,0 +1,6 @@
+import"prelude/arith.kmp"
+
+mod_kmp : Int Int -- Int
+        =: [ modInt ]
+
+%foreign cabi mod_kmp
diff --git a/test/golden/mod.out b/test/golden/mod.out
new file mode 100644
--- /dev/null
+++ b/test/golden/mod.out
@@ -0,0 +1,1 @@
+-1
diff --git a/test/harness/mod.c b/test/harness/mod.c
new file mode 100644
--- /dev/null
+++ b/test/harness/mod.c
@@ -0,0 +1,7 @@
+#include <stdio.h>
+
+extern int mod_kmp(int, int);
+
+int main(int argc, char *argv[]) {
+    printf("%d\n", mod_kmp(2, -3));
+}
