Skip to content
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

logic hierarchy #421

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 220 additions & 0 deletions symbolic-base/src/ZkFold/Base/Data/Logical.hs
TurtlePU marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

module ZkFold.Base.Data.Logical
( Boolean (..)
, all, any, and, or
, Conditional (..)
, ifThenElse
, Eq (..)
, elem
, GConditional (..)
, GEq (..)
) where

import Control.Category
import qualified Data.Bool as H
import Data.Foldable hiding (all, and, any, elem, or)
import Data.Functor.Identity
import qualified Data.Functor.Rep as R
import Data.Kind
import qualified GHC.Generics as G
import Prelude (type (~))
import qualified Prelude as H

import ZkFold.Base.Algebra.Basic.Number
import ZkFold.Base.Data.Vector

class Boolean b where
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some documentation describing the laws of the class could be useful here


true, false :: b

not :: b -> b

(&&), (||), xor :: b -> b -> b

instance Boolean H.Bool where
true = H.True
false = H.False
not = H.not
(&&) = (H.&&)
(||) = (H.||)
xor = (H./=)

all :: (Boolean b, Foldable t) => (x -> b) -> t x -> b
TurtlePU marked this conversation as resolved.
Show resolved Hide resolved
all f = foldr ((&&) . f) true

any :: (Boolean b, Foldable t) => (x -> b) -> t x -> b
any f = foldr ((||) . f) false

and :: (Boolean b, Foldable t) => t b -> b
and = all id

or :: (Boolean b, Foldable t) => t b -> b
or = any id

class Boolean (BooleanOf a) => Conditional a where

type BooleanOf a :: Type
type BooleanOf a = GBooleanOf (G.Rep a)

bool :: a -> a -> BooleanOf a -> a
default bool ::
( G.Generic a
, GConditional (G.Rep a)
, BooleanOf a ~ GBooleanOf (G.Rep a)
) => a -> a -> BooleanOf a -> a
bool f t b = G.to (gbool (G.from f) (G.from t) b)

instance Conditional y => Conditional (x -> y) where
type BooleanOf (x -> y) = BooleanOf y
bool f t b x = bool (f x) (t x) b

instance
( Conditional x0
, Conditional x1
, BooleanOf x0 ~ BooleanOf x1
) => Conditional (x0,x1)

instance
( Conditional x0
, Conditional x1
, Conditional x2
, BooleanOf x0 ~ BooleanOf x1
, BooleanOf x1 ~ BooleanOf x2
) => Conditional (x0,x1,x2)

instance
( Conditional x0
, Conditional x1
, Conditional x2
, Conditional x3
, BooleanOf x0 ~ BooleanOf x1
, BooleanOf x1 ~ BooleanOf x2
, BooleanOf x2 ~ BooleanOf x3
) => Conditional (x0,x1,x2,x3)

instance (R.Representable v, Conditional x)
=> Conditional (G.Generically1 v x) where
type BooleanOf (G.Generically1 v x) = BooleanOf x
bool (G.Generically1 vf) (G.Generically1 vt) b =
G.Generically1 (R.mzipWithRep (\f t -> bool f t b) vf vt)

deriving via G.Generically1 (Vector n) x
instance (KnownNat n, Conditional x) => Conditional (Vector n x)
Copy link
Contributor Author

@echatav echatav Dec 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Conditional (as well as Eq) instance for Vectors is derived via the Generically1 newtype. This is because the instance definition works for any Representable (and Foldable) over a Conditional (and Eq). But the choice of the Generically1 newtype is arbitrary. Similarly the choice of the Identity newtype for deriving Conditional (and Eq) instances from their Prelude variants is arbitrary.

Are there better newtypes to use for this purpose? Maybe new Representably and Prelusively newtypes are warranted?

Copy link
Contributor

@TurtlePU TurtlePU Dec 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Generically1 is fine here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's better to use something else instead of Identity for Haskell types though...


instance Conditional (Identity x) where
type BooleanOf (Identity x) = H.Bool
bool = H.bool

ifThenElse :: Conditional a => BooleanOf a -> a -> a -> a
TurtlePU marked this conversation as resolved.
Show resolved Hide resolved
ifThenElse b t f = bool f t b

class Conditional a => Eq a where

(==) :: a -> a -> BooleanOf a
default (==) ::
( G.Generic a
, GEq (G.Rep a)
, BooleanOf a ~ GBooleanOf (G.Rep a)
) => a -> a -> BooleanOf a
x == y = geq (G.from x) (G.from y)

(/=) :: a -> a -> BooleanOf a
TurtlePU marked this conversation as resolved.
Show resolved Hide resolved
default (/=) ::
( G.Generic a
, GEq (G.Rep a)
, BooleanOf a ~ GBooleanOf (G.Rep a)
) => a -> a -> BooleanOf a
x /= y = gneq (G.from x) (G.from y)

instance
( Eq x0, Eq x1
, BooleanOf x0 ~ BooleanOf x1
) => Eq (x0,x1)

instance
( Eq x0
, Eq x1
, Eq x2
, BooleanOf x0 ~ BooleanOf x1
, BooleanOf x1 ~ BooleanOf x2
) => Eq (x0,x1,x2)

instance
( Eq x0
, Eq x1
, Eq x2
, Eq x3
, BooleanOf x0 ~ BooleanOf x1
, BooleanOf x1 ~ BooleanOf x2
, BooleanOf x2 ~ BooleanOf x3
) => Eq (x0,x1,x2,x3)

instance (Foldable v, R.Representable v, Eq x)
=> Eq (G.Generically1 v x) where
G.Generically1 vx == G.Generically1 vy =
and (R.mzipWithRep (==) vx vy)
G.Generically1 vx /= G.Generically1 vy =
or (R.mzipWithRep (==) vx vy)

deriving via G.Generically1 (Vector n) x
instance (KnownNat n, Eq x) => Eq (Vector n x)

instance H.Eq x => Eq (Identity x) where
(==) = (H.==)
(/=) = (H./=)

elem :: (Eq a, Foldable t) => a -> t a -> BooleanOf a
elem x = any (== x)

class Boolean (GBooleanOf f) => GConditional f where
Copy link
Contributor

@TurtlePU TurtlePU Dec 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: move deriving stuff to corresponding .Generic/.Deriving modules (e.g. ZkFold.Base.Data.Eq.Generic or ZkFold.Base.Data.Eq.Deriving) to make modules shorter and more readable. I guess instances for K1 would still need to be defined in the main module, but this still would look cleaner and understandable (because it would be near instances of the main class for tuples which need it)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So 5 modules instead of 1? 🙃 I don't understand how this would work and not require mutually importing modules. I split it into 3 for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, maybe at least visually separate the part on Generic deriving from the rest by a comment

type GBooleanOf f :: Type
gbool :: f a -> f a -> GBooleanOf f -> f a

instance
( GConditional u
, GConditional v
, Boolean (GBooleanOf u)
, GBooleanOf u ~ GBooleanOf v
) => GConditional (u G.:*: v) where
type GBooleanOf (u G.:*: v) = GBooleanOf u
gbool (f0 G.:*: f1) (t0 G.:*: t1) b = gbool f0 t0 b G.:*: gbool f1 t1 b

instance (R.Representable v, GConditional u)
=> GConditional (v G.:.: u) where
type GBooleanOf (v G.:.: u) = GBooleanOf u
gbool (G.Comp1 vf) (G.Comp1 vt) b =
G.Comp1 (R.mzipWithRep (\f t -> gbool f t b) vf vt)

instance GConditional v => GConditional (G.M1 i c v) where
type GBooleanOf (G.M1 i c v) = GBooleanOf v
gbool (G.M1 f) (G.M1 t) b = G.M1 (gbool f t b)

instance Conditional x => GConditional (G.K1 i x) where
type GBooleanOf (G.K1 i x) = BooleanOf x
gbool (G.K1 f) (G.K1 t) b = G.K1 (bool f t b)

class GConditional f => GEq f where
geq :: f a -> f a -> GBooleanOf f
gneq :: f a -> f a -> GBooleanOf f

instance
( GEq u, GEq v
, GBooleanOf u ~ GBooleanOf v
) => GEq (u G.:*: v) where
geq (x0 G.:*: x1) (y0 G.:*: y1) = geq x0 y0 && geq x1 y1
gneq (x0 G.:*: x1) (y0 G.:*: y1) = gneq x0 y0 || gneq x1 y1

instance (Foldable v, R.Representable v, GEq u) => GEq (v G.:.: u) where
geq (G.Comp1 vx) (G.Comp1 vy) = and (R.mzipWithRep geq vx vy)
gneq (G.Comp1 vx) (G.Comp1 vy) = or (R.mzipWithRep gneq vx vy)

instance GEq v => GEq (G.M1 i c v) where
geq (G.M1 x) (G.M1 y) = geq x y
gneq (G.M1 x) (G.M1 y) = gneq x y

instance Eq x => GEq (G.K1 i x) where
geq (G.K1 x) (G.K1 y) = x == y
gneq (G.K1 x) (G.K1 y) = x /= y
1 change: 1 addition & 0 deletions symbolic-base/symbolic-base.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ library
ZkFold.Base.Data.Functor.Rep
ZkFold.Base.Data.HFunctor
ZkFold.Base.Data.List.Infinite
ZkFold.Base.Data.Logical
ZkFold.Base.Data.Matrix
ZkFold.Base.Data.Orphans
ZkFold.Base.Data.Package
Expand Down