content: {-# LANGUAGE ScopedTypeVariables #-}
  
  -- | copied from https://raw.githubusercontent.com/Peaker/lamdu/wip_integration/bottlelib/Data/Function/Decycle.hs
  
  module Infernu.Decycle(decycleOn, decycle, decycle2, decycle3) where
  
  import Infernu.Prelude
  import qualified Data.Set as Set
  
  -- | A fix for functions that terminates recursive cycles
  decycleOn :: forall a b res . Ord b => (a -> b) -> (Maybe (a -> res) -> a -> res) -> a -> res
  decycleOn toOrd f = go Set.empty
    where
      go :: Set.Set b -> a -> res
      go visited x = f (mRecurse visited (toOrd x)) x
  
      mRecurse :: Set.Set b -> b -> Maybe (a -> res)
      mRecurse visited o = if Set.member o visited
                           then Nothing
                           else Just $ go (Set.insert o visited)
  
  decycle :: Ord a => (Maybe (a -> res) -> a -> res) -> a -> res
  decycle = decycleOn id
  
  decycle2 :: (Ord b, Ord a) => (Maybe (a -> b -> c) -> a -> b -> c) -> a -> b -> c
  decycle2 f arg1 arg2 = decycle (\recurse (arg1', arg2') -> f (curry <$> recurse) arg1' arg2') (arg1, arg2)
  
  decycle3 :: (Ord b, Ord a, Ord c) => (Maybe (a -> b -> c -> res) -> a -> b -> c -> res) -> a -> b -> c -> res
  decycle3 f arg1 arg2 arg3 = decycle f3 (arg1, arg2, arg3)
    where f3 Nothing (x,y,z) = f Nothing x y z
          f3 (Just rec3) (x,y,z) = f (Just (\a b c -> rec3 (a,b,c))) x y z
  