Skip to content

Commit

Permalink
generic sequenceA operation for tuples without Monoid requirement
Browse files Browse the repository at this point in the history
  • Loading branch information
kostmo committed Jul 18, 2023
1 parent 88c2c21 commit b1a771e
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 9 deletions.
11 changes: 2 additions & 9 deletions src/Swarm/Game/Scenario/Topography/Navigation/Portal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import Linear (negated)
import Swarm.Game.Location
import Swarm.Game.Scenario.Topography.Navigation.Waypoint
import Swarm.Game.Universe
import Swarm.Util (allEqual, binTuples, both, quote)
import Swarm.Util (allEqual, binTuples, both, quote, sequenceTuple)

type WaypointMap = M.Map WaypointName (NonEmpty Location)

Expand Down Expand Up @@ -244,18 +244,11 @@ ensureSpatialConsistency xs =

tuplify = both (view subworld) &&& both (view planar)

nest ::
Signed (b, a) ->
(b, Signed a)
nest = \case
Positive x -> fmap Positive x
Negative x -> fmap Negative x

reExtract = \case
Positive x -> x
Negative x -> negated x

groupedBySubworldPair = binTuples $ map (nest . fmap tuplify) normalizedOrdering
groupedBySubworldPair = binTuples $ map (sequenceTuple . fmap tuplify) normalizedOrdering
vectorized = M.map (NE.map (reExtract . fmap (uncurry (.-.)))) groupedBySubworldPair

nonUniform = M.filter ((not . allEqual) . NE.toList) vectorized
17 changes: 17 additions & 0 deletions src/Swarm/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module Swarm.Util (
findDup,
both,
allEqual,
sequenceTuple,

-- * Directory utilities
readFileMay,
Expand Down Expand Up @@ -193,6 +194,22 @@ both f = bimap f f
allEqual :: (Ord a) => [a] -> Bool
allEqual = (== 1) . S.size . S.fromList

-- | This function has a lamentable basis.
-- The 'sequenceA' function requires an 'Applicative' instance
-- for the inner functor. However, the 'Applictaive' instance
-- of @(,)@ (the two-element tuple) requires a 'Monoid' instance
-- for the first element!
-- See: https://hackage.haskell.org/package/base-4.18.0.0/docs/src/GHC.Base.html#line-523
--
-- The 'sequenceA' operation does not affect the first element
-- of the tuple, so it shouldn't matter whether it has a 'Monoid' instance!
-- To satisfy the compiler, we abuse a list to wrap and then unwrap after a traversal.
sequenceTuple ::
Traversable f =>
f (a, b) ->
(a, f b)
sequenceTuple = first head . traverse (first pure)

------------------------------------------------------------
-- Directory stuff

Expand Down

0 comments on commit b1a771e

Please sign in to comment.