-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Constraints and defining foldMap
for a free category?
#9
Comments
Nice question! But first: the |
Second: this isn't really the free category, is it? Shouldn't we normalize any composition to the form Though I suppose that doesn't have any bearing on the question here. ...Well, maybe it does actually matter. At any rate, surely it should be
not |
To get to the actual crux: adding constraints to
That could probably be made to work, but it's mighty awkward. We could, I suppose, introduce a wrapper for this kind of "constraint-propagating mapping", either
or
But this still feels like clunky and missing the point of what we're really trying to do. Perhaps this should just be a |
Minor clarifications
It's there on purpose in a very weak sense ¯\(ツ)/¯. Both
Yes - list constructors should be replaced with corresponding category operations. Thanks for catching that! How should
I can see why that makes sense - it exposes the relevant type variables and should generalize to arbitrary length constructions. However, I'm not sure concretely what you had in mind:
Once I expand the pattern matching, it seems - as you put it with respect to constraint propagation - that the redundancy between
data FreePathCons p a b where
Nil ∷ FreePathCons p a a
(:<) ∷ b `p` c → FreePathCons p a b → FreePathCons p a c
data FreePathEmb p a b where
Emb ∷ a `p` b → FreePathEmb p a b
Nil ∷ FreePathEmb p a a
(:<:) ∷ FreePathEmb p b c → FreePathEmb p a b → FreePathEmb p a c Option 1 complicates the definition of a Your remarks in your most recent comment also suggest to me a third option:
data FreePathCirc p a b where
Emb ∷ a `p` b → FreePathCirc p a b
Nil ∷ FreePathCirc p a a
(:><) ∷ FreePathCirc p c d → FreePathCirc p a b → FreePathCirc p b c → FreePathCirc p a d
-- NB argument order of `:><` mimics the convention that has emerged for the arguments
-- of `dimap` modulo keeping the order of composition (dataflow direction) the same as (.) Constraint propagation and a concise formulation of On that note - profunctors (not their definition in most Haskell packages per se, where obligatory contamination by That's a simplification for now, but a defect in light of some of the use cases that motivate this package, no? Perhaps accounting for this may also point towards better formulations of
I will have to think more about this (and read enough about natural transformations to see exactly where the relevant types would line up), but my first thought is that some of the constructions in the |
Poking around further in other parts of the constrained-categories repo and nlab got me to this functor-based definition that compiles: {-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE UndecidableSuperClasses #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
import Prelude hiding
(id, (.), Functor, fmap, Foldable, foldMap)
import Control.Category.Constrained
(Category (Object, id, (.)))
infixr 9 ∘
(∘) ∷ (Category k, Object k a, Object k b, Object k c)
⇒ b `k` c → a `k` b → a `k` c
(∘) = (.)
data FreePath p a b where
Emb ∷ (Category p, Object p a, Object p b) -- aka singleton
⇒ a `p` b → FreePath p a b
Id ∷ (a ~ b, Category p, Object p a, Object p b) -- aka []/mempty
⇒ FreePath p a b
Of ∷ (Category p, Object p a, Object p b, Object p x) -- aka (++)/(<>)
⇒ FreePath p x b → FreePath p a x → FreePath p a b
class Object p a ⇒ Object' p a
instance Object p a ⇒ Object' p a
-- | @φ@ represents some functor's mapping of objects of @r@ to objects of @t@;
-- @α@ represents the same functor's mapping of morphisms of @r@ to morphisms of @t@.
foldMap ∷ ∀ φ r t a b. (Category r, Category t, Object r a, Object r b
, ∀ z. Object' r z ⇒ Object' t (φ z))
⇒ (∀ x y. (x `r` y) → (φ x `t` φ y))
-- NB The three alternative type signatures below will also compile;
-- I believe the next two below are equivalent to each other.
-- ⇒ (∀ x y. ((Object r x, Object r y, Object t (φ x), Object t (φ y))
-- ⇒ (x `r` y) → (φ x `t` φ y)))
-- ⇒ (∀ x y. ((Object r x, Object r y) ⇒ (x `r` y)
-- → ((Object t (φ x), Object t (φ y)) ⇒ (φ x `t` φ y))))
-- ⇒ (∀ x y. (Object r x, Object r y, ∀ w. Object' r w ⇒ Object' t (φ w))
-- ⇒ (x `r` y) → (φ x `t` φ y))
→ FreePath r a b → (φ a `t` φ b)
foldMap α (Emb p) = α p
foldMap _ Id = id
foldMap α (q `Of` p) = (foldMap α q) ∘ (foldMap α p) Note that GADT constructor constraints and a ∀ z. Object' r z ⇒ Object' t (φ z) constraint (enabled by what you mention at the end of this comment of yours) are crucial for allowing What kind of functor typeclass were you thinking that would avoid the need for existential types? The analogue of the (four) type(s) for {- | A functor @φ@ from @r@ to @t@
1. should map every object @a@ of @r@ to an object @φ a@ of @t@.
2. should map every morphism @a `r` b@ to a morphism @φ a `t` φ b@.
#1 is taken care of in Haskell by the data constructors of @φ@; #2 is the job of the definition
of @fmap@ in the @Functor@ instance for @φ@, and every definition for @fmap@ should ensure the
following equalities hold:
@
fmap (g ∘ f) = fmap g ∘ fmap f
fmap id = id
@
-}
class (Category r, Category t) ⇒ Functor φ r t where
fmap ∷ ∀ a b. (a `r` b) → (φ a `t` φ b)
-- fmap ∷ ∀ a b. (Object r a, Object t (φ a), Object r b, Object t (φ b))
-- ⇒ (a `r` b) → (φ a `t` φ b)
-- fmap ∷ ∀ a b. ((Object r a, Object r b) ⇒ (a `r` b)
-- → ((Object t (φ a), Object t (φ b)) ⇒ ((φ a) `t` (φ b))))
-- fmap ∷ ∀ a b. (Object r a, Object r b, ∀ x. Object' r x ⇒ Object' t (φ x))
-- ⇒ (a `r` b) → (φ a `t` φ b) My few attempts at extending |
Some lightly cleaned up definitions that support lifting a type that isn't already a category into the Revised definitions {-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE StandaloneDeriving #-}
import Control.Category.Constrained
(Category (Object, id, (.)))
import Data.Functor.Identity
import qualified Prelude as Pr
import Prelude hiding
(id, (.), Functor, fmap, Foldable, foldMap)
import Data.Kind (Type, Constraint)
infixr 9 ∘
(∘) ∷ (Category m, Object m a, Object m b, Object m c)
⇒ b `m` c → a `m` b → a `m` c
(∘) = (.)
-- @p@ is expected but not required to be a category here: @φ@ corresponds to the constraint (if any)
-- on objects of the category.
data FreePath (p ∷ Type → Type → Type) (φ ∷ Type → Constraint) (a ∷ Type) (b ∷ Type) where
Id ∷ (φ a, φ b, a ~ b) ⇒ FreePath p φ a b
Of ∷ (φ a, φ b, φ x ) ⇒ FreePath p φ x b → FreePath p φ a x → FreePath p φ a b
Emb ∷ (φ a, φ b ) ⇒ a `p` b → FreePath p φ a b
class Object p a ⇒ Object' p a
instance Object p a ⇒ Object' p a
-- | If values of @FreePath p@ represent 'programs' over the primitives offered by @p@, this instance allows
-- for the definition of 'program transformations' ("endofuctors") via @foldMap@.
instance Category p ⇒ Category (FreePath p (Object' p)) where
type Object (FreePath p (Object' p)) o = Object p o
id = Id
(.) = Of
-- | @γ@ represents some functor's mapping of objects of @r@ to objects of @t@;
-- @α@ represents the same functor's mapping of morphisms of @r@ to morphisms of @t@.
-- Note that @r@ is expected but not required to be a Category with some object
-- constraint @φ@; i.e. if @r@ is a (constrained) Category instance, than it is
-- expected that @φ@ = @Object r@/that @φ x@ ≡ @Object r x@.
foldMap ∷ ∀ φ γ r t a b. (Category t, φ a, φ b, ∀ z. φ z ⇒ Object' t (γ z))
⇒ (∀ x y. (x `r` y) → (γ x `t` γ y))
→ FreePath r φ a b → (γ a `t` γ b)
foldMap α (Emb p) = α p
foldMap _ Id = id
foldMap α (q `Of` p) = (foldMap α q) ∘ (foldMap α p) Some arithmetic++ primitives -- | A variant on the common arithmetic++ toy/demo DSL, bashed into the shape of
-- unary functions.
-- This may be too simple to be the most useful demo/test case - *every* constructor either explicitly
-- requires the category constraint or it specifies a concrete type that has an explicitly given instance;
-- A more usefully interesting test case may be to define a more typical arithmetic expression DSL and then
-- define some (unary) morphisms.
data ArithFunc a b where
Lit ∷ (ArithPrim b) ⇒ b → ArithFunc () b
Inc ∷ ArithFunc Int Int
Dec ∷ ArithFunc Int Int
EqlTo ∷ (ArithPrim a) ⇒ a → ArithFunc a Bool
Ite ∷ (ArithPrim b) ⇒ b → b → ArithFunc Bool b
deriving instance (Show (ArithFunc a b))
class (Show x, Eq x) ⇒ ArithPrim x
instance ArithPrim Int
instance ArithPrim Bool
instance ArithPrim () Some example expressions Below are some example programs in the DSL defined by lifting ArithFunc primitives into the free category ('quiver'?) definable via FreePath: type FreeArithFunc a b = FreePath ArithFunc ArithPrim a b
noOp ∷ ArithPrim a ⇒ FreeArithFunc a a
noOp = Id
one ∷ FreeArithFunc () Int
one = Emb $ Lit 1
two ∷ FreeArithFunc () Int
two = Emb $ Lit 2
sub1 ∷ FreeArithFunc Int Int
sub1 = Emb Dec
alsoOne ∷ FreeArithFunc () Int
alsoOne = sub1 `Of` two
alsoOneIsOne ∷ FreeArithFunc () Bool
alsoOneIsOne = Emb (EqlTo 1) `Of` alsoOne
boolToInt ∷ FreeArithFunc Bool Int
boolToInt = Emb (Ite 1 0)
alsoOneIsOne' ∷ FreeArithFunc () Int
alsoOneIsOne' = boolToInt `Of` alsoOneIsOne `Of` noOp
-- This would be the `fmap` definition for a functor instance relating `Identity`, `ArithFunc`, and `Hask`
evalCalc ∷ ∀ a b. ArithFunc a b → (Identity a → Identity b)
evalCalc (Lit b) = Pr.fmap $ const b
evalCalc Inc = Pr.fmap $ (+ 1)
evalCalc Dec = Pr.fmap $ (subtract 1)
evalCalc (EqlTo a) = Pr.fmap $ (== a)
evalCalc (Ite t e) = Pr.fmap $ \test → if test then t else e
evalCalc_ ∷ ∀ a b. (ArithPrim a, ArithPrim b)
⇒ FreePath ArithFunc ArithPrim a b
→ (Identity a → Identity b)
evalCalc_ = foldMap evalCalc
unId ∷ (Identity a → Identity b) → (a → b)
unId f = runIdentity ∘ f ∘ Identity
-- More convenient to work with for checking that you can actually perform calculations
evalCalc' ∷ ∀ a b. (ArithPrim a, ArithPrim b)
⇒ FreePath ArithFunc ArithPrim a b
→ (a → b)
evalCalc' f = unId $ evalCalc_ f As far as |
I'm afraid I won't have time to properly think about this in the next weeks, but eventually I will! |
How would you suggest defining
foldMap
for a type-aligned list ("value of the free category over somep a b
"), given the definitions below?I think I understand the basic issue as the typechecker needing some reason to believe that every single intermediate type
y
in a sequence(p y z) :< (p x y)
needs to satisfy the constraintObject q
.However, I'm not sure what some reasonable conditions are under which that would hold or how to express them:
Object q
is the trivial or vacuous constraint, this should be definable.p
is already aCategory
instance andp
andq
are such thatObject p z
always entails thatObject q z
holds, this should be definable.Does the embedding constructor (
:<
) ofFreePath
need to be constrained? If so, how?EDIT: Corrected the RHSs of the
:<:
case offoldMap
,foldMap'
.The text was updated successfully, but these errors were encountered: