Skip to content

Commit

Permalink
address sequenceTuple issue
Browse files Browse the repository at this point in the history
  • Loading branch information
kostmo committed Jul 19, 2023
1 parent ea93e46 commit 659c7a0
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 19 deletions.
38 changes: 36 additions & 2 deletions src/Swarm/Game/Scenario/Topography/Navigation/Portal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import Linear (V2, negated)
import Swarm.Game.Location
import Swarm.Game.Scenario.Topography.Navigation.Waypoint
import Swarm.Game.Universe
import Swarm.Util (allEqual, binTuples, both, failT, quote, sequenceTuple)
import Swarm.Util (allEqual, binTuples, both, failT, quote)

type WaypointMap = M.Map WaypointName (NonEmpty Location)

Expand Down Expand Up @@ -249,10 +249,44 @@ ensureSpatialConsistency xs =

groupedBySubworldPair ::
Map (SubworldName, SubworldName) (NonEmpty (Signed (Location, Location)))
groupedBySubworldPair = binTuples $ map (sequenceTuple . fmap tuplify) normalizedOrdering
groupedBySubworldPair = binTuples $ map (sequenceSigned . fmap tuplify) normalizedOrdering

vectorized :: Map (SubworldName, SubworldName) (NonEmpty (V2 Int32))
vectorized = M.map (NE.map (getSigned . fmap (uncurry (.-.)))) groupedBySubworldPair

nonUniform :: Map (SubworldName, SubworldName) (NonEmpty (V2 Int32))
nonUniform = M.filter ((not . allEqual) . NE.toList) vectorized

-- |
-- An implementation of 'sequenceA' for 'Signed' that does not
-- require an 'Applicative' instance for the inner 'Functor'.
--
-- == Discussion
-- Compare to the 'Traversable' instance of 'Signed':
-- @
-- instance Traversable Signed where
-- traverse f (Positive x) = Positive <$> f x
-- traverse f (Negative x) = Negative <$> f x
-- @
--
-- if we were to substitute 'id' for f:
-- @
-- traverse id (Positive x) = Positive <$> id x
-- traverse id (Negative x) = Negative <$> id x
-- @
-- our implementation essentially becomes @traverse id@.
--
-- However, we cannot simply write our implementation as @traverse id@, because
-- the 'traverse' function has an 'Applicative' constraint, which is superfluous
-- for our purpose.
--
-- Perhaps there is an opportunity to invent a typeclass for datatypes which
-- consist exclusively of unary (or more ambitiously, non-nullary?) data constructors,
-- for which a less-constrained 'sequence' function could be automatically derived.
sequenceSigned ::
Functor f =>
Signed (f a) ->
f (Signed a)
sequenceSigned = \case
Positive x -> Positive <$> x
Negative x -> Negative <$> x
17 changes: 0 additions & 17 deletions src/Swarm/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ module Swarm.Util (
findDup,
both,
allEqual,
sequenceTuple,

-- * Directory utilities
readFileMay,
Expand Down Expand Up @@ -195,22 +194,6 @@ allEqual :: (Ord a) => [a] -> Bool
allEqual [] = True
allEqual (x : xs) = all (== x) xs

-- | This function has a lamentable basis.
-- The 'sequenceA' function requires an 'Applicative' instance
-- for the inner 'Functor'. However, the 'Applicative' 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 first 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 659c7a0

Please sign in to comment.