lude

Haskell prelude alternative
git clone git://git.rr3.xyz/lude | git clone gits://git.rr3.xyz/lude
Log | Files | Refs | LICENSE

commit 37ebb0a47f875b1bbd792aaca9dd0becff683c00
parent 1149fa76f683442b27d12b822661b1043fe8317b
Author: Robert Russell <robert@rr3.xyz>
Date:   Sun,  1 Jun 2025 21:19:16 -0700

Add stuff from experiments

Uhhgggg! I think I'm giving up on this project for now. The scope
has grown to creating a Haskell base alternative that puts laziness
and strictness on an equal footing, but that is not possible in an
ergonomic way without something like 2LTT.

Diffstat:
Mpackage.yaml | 52+++++++++++++++++++++++++++++-----------------------
Msrc/Lude.hs | 436++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 430 insertions(+), 58 deletions(-)

diff --git a/package.yaml b/package.yaml @@ -4,32 +4,38 @@ author: "Robert Russell" license: ISC ghc-options: - - -Wall - - -Wno-name-shadowing - - -Wno-missing-signatures + - -Wall + - -Wno-name-shadowing + - -Wno-missing-signatures + - -Wno-unused-top-binds + - -fprint-explicit-foralls + - -fprint-explicit-kinds + - -fprint-explicit-runtime-reps + +language: GHC2021 default-extensions: - - NoImplicitPrelude - - BangPatterns - - TypeFamilies - - TypeOperators -#- DuplicateRecordFields -#- NoFieldSelectors -#- OverloadedRecordDot -#- BlockArguments -#- LambdaCase -#- MultiWayIf -#- OverloadedStrings -#- ScopedTypeVariables -#- FlexibleInstances -#- MultiParamTypeClasses -#- UndecidableInstances + - BlockArguments + - DataKinds + - DefaultSignatures + - FunctionalDependencies + - GADTs + - LambdaCase + - MagicHash + - NoImplicitPrelude + - PatternSynonyms + - TypeData + - TypeFamilies + - UnboxedTuples + - UnicodeSyntax + - UnliftedNewtypes + - UnliftedDatatypes dependencies: - - base - - bytestring - - text - - mtl + - base + #- bytestring + #- text + #- mtl library: - source-dirs: src + source-dirs: src diff --git a/src/Lude.hs b/src/Lude.hs @@ -1,20 +1,40 @@ module Lude ( + -- Types, + Constraint, + Levity (..), + RuntimeRep (..), + Thunked, + LRep, + URep, + Type, + BoxedType, + LType, + UType, + Kind, + Unlift (..), -- Function const, fix, flip, - ($), - ($!), - (&), - (&!), + -- ($), + -- ($!), + -- (&), + -- (&!), + -- Bool + IsBool (..), + Bool (..), + (&&), + (||), + not, -- Category - Category ((.)), - identity, - (<<), - (>>), + -- Category (identity, compose), + -- (<.), + -- (.>), -- Functor + {- Functor, void, + Compose (Compose, getCompose), Const (Const, getConst), Identity (Identity, runIdentity), map, @@ -52,19 +72,61 @@ module Lude ( (^$!^), plug', (^&!^), + -} ) where -import Control.Applicative (Applicative (liftA2, pure, (<*>)), (<**>)) -import Control.Category (Category (id, (.))) +import Control.Applicative (Alternative (empty, (<|>)), Applicative (liftA2, pure, (<*>)), (<**>)) import Control.Monad (Monad ((>>=))) +import Data.Bool (Bool (..), not, (&&), (||)) import Data.Foldable (Foldable) import Data.Function (const, fix, flip) import Data.Functor (Functor (fmap), void) +import Data.Functor.Compose (Compose (Compose, getCompose)) import Data.Functor.Const (Const (Const, getConst)) import Data.Functor.Identity (Identity (Identity, runIdentity)) -import Data.Maybe (Maybe) +import Data.Maybe (Maybe (..)) import Data.Traversable (Traversable (sequenceA)) +-- TODO: SIMD: VecCount (..) and VecElem (..) +import GHC.Exts (Constraint, Levity (..), RuntimeRep (..)) +import GHC.Exts qualified as GHC + +infixl 8 ∙ +type (∙) f a = f a + +-------------------------------------------------------------------------------- +-- Types + +-- | An alias for the empty constraint. +-- +-- Used to declare unlifted values at the top-level. For example, for any @r@ +-- and @A :: Type r@: +-- @ +-- a :: Thunked => A +-- a = ... +-- @ +type Thunked = () :: Constraint + +type LRep = BoxedRep Lifted +type URep = BoxedRep Unlifted + +type Type = GHC.TYPE +type BoxedType l = Type (BoxedRep l) +type LType = BoxedType Lifted +type UType = BoxedType Unlifted + +-- | An alias for `LType`. +type Kind = LType + +-- | Types with an unlifted alternative. +-- +-- Laws: +-- - @force (lift x) = x@ +class Unlift a where + type U a :: UType + force :: a -> U a + lift :: U a -> a + -------------------------------------------------------------------------------- -- Function @@ -90,36 +152,275 @@ x & f = f x !x &! f = f x -------------------------------------------------------------------------------- --- Category +-- Bool -infixr 2 <<, >> +class IsBool (bool :: Type r) where + true :: (Thunked) => bool + false :: (Thunked) => bool + bool :: a -> a -> bool -> a --- | The identity morphism. --- --- This is just a renamed version of @Control.Category.id@. -identity :: (Category v) => v a a -identity = id +instance IsBool Bool where + true = True + false = False + bool t f = \case + True -> t + False -> f + +data UBool :: UType where + UTrue :: UBool + UFalse :: UBool + +instance IsBool UBool where + true = UTrue + false = UFalse + bool t f = \case + UTrue -> t + UFalse -> f + +instance Unlift Bool where + type U Bool = UBool + + force True = UTrue + force False = UFalse + + lift UTrue = True + lift UFalse = False + +-------------------------------------------------------------------------------- +-- Maybe + +class IsMaybe (maybe :: Type s -> Type r) where + just :: a -> maybe a + nothing :: (Thunked) => maybe a + maybe :: b -> (a -> b) -> maybe a -> b + +instance IsMaybe Maybe where + just = Just + nothing = Nothing + maybe n j = \case + Just a -> j a + Nothing -> n + +data UMaybe :: LType -> UType where + UJust :: a -> UMaybe a + UNothing :: UMaybe a + +instance IsMaybe UMaybe where + just = UJust + nothing = UNothing + maybe n j = \case + UJust a -> j a + UNothing -> n + +instance Unlift (Maybe a) where + type U (Maybe a) = UMaybe a + + force (Just a) = UJust a + force Nothing = UNothing + + lift (UJust a) = Just a + lift UNothing = Nothing + +data Maybe' :: UType -> LType where + Just' :: a -> Maybe' a + Nothing' :: Maybe' a + +instance IsMaybe Maybe' where + just = Just' + nothing = Nothing' + maybe n j = \case + Just' a -> j a + Nothing' -> n + +data UMaybe' :: UType -> UType where + UJust' :: a -> UMaybe' a + UNothing' :: UMaybe' a + +instance IsMaybe UMaybe' where + just = UJust' + nothing = UNothing' + maybe n j = \case + UJust' a -> j a + UNothing' -> n + +instance Unlift (Maybe' a) where + type U (Maybe' a) = UMaybe' a + + force (Just' a) = UJust' a + force Nothing' = UNothing' + + lift (UJust' a) = Just' a + lift UNothing' = Nothing' + +isJust :: forall s r (maybe :: Type s -> Type r) a. (IsMaybe maybe) => maybe a -> Bool +isJust = maybe False (const True) + +isNothing :: (IsMaybe maybe) => maybe a -> Bool +isNothing = maybe True (const False) + +-- type family Maybe# (r :: RuntimeRep) (l :: Levity) :: Type r -> BoxedType l +-- type instance Maybe# LRep Lifted = Maybe +-- type instance Maybe# URep Lifted = Maybe' +-- type instance Maybe# LRep Unlifted = UMaybe +-- type instance Maybe# URep Unlifted = UMaybe' + +-- type family Lower (a :: Kind) :: Kind where +-- Lower (BoxedType l) = TypeU +-- Lower (k -> l) = k -> Lower l +-- type family X (a :: k) :: Lower k +-- type instance X (MaybeL @l) = MaybeL @Unlifted + +-------------------------------------------------------------------------------- +-- Numeric types + +-- TODO + +-------------------------------------------------------------------------------- +-- Category + +-- infixr 9 ∘ + +-- infixr 2 <. +-- infixl 2 .> + +-- (⊲⊳) +-- (⋖⋗) +-- (⩹⩺) +-- (⨟) +-- (⦂) +-- (⋄) +-- (∧∨) +-- (⩓⩔) +-- (∩∪) +-- (=≠≡≢≈≉) +-- (<>≤≥≮≯≰≱) +-- (≺≻≼≽⊀⊁⋠⋡) +-- (⊂⊃⊆⊇⊄⊅⊈⊉) +-- (⊏⊐⊑⊒) +-- (⊢⊣⊤⊥) +-- y = h ∘ g ∘ f ∙^ x +-- (f ⦾ g ⦿ x) +-- y = h ◁ g ◁ f ◀ x +-- y = x ▶ f ▷ g ▷ h +-- (⧑⧒⧓⧔⧕⧖⧗⋈⋉⋊) +-- (↑↓↔↕↖↗↘↙) +-- (⥼⥽⥾⥿) +-- (⊰⊱) +-- (⭘∘) +-- (⦁∙⋅) +-- (⊙) +-- (⊚) +-- (⬬⬭⬮⬯) +-- (⬝⬞) +-- (⋆) +-- (⬥⬦⬧⬨⬩⬪⬫) +-- (◖◗) +-- (◐◑) +-- (⫾) +-- (■▪▬▮▰▲▶▼◀◆) +-- (∙⋆⋄*≀⌑▫) +-- y = x ▪ f ▫ g ▫ h +-- y = x ⦂ f ⨟ g ⨟ h + +-- TODO: Op, Products, Sums, Clown, Joker + +{- + +class Category (mor :: obj -> obj -> TypeL) where + -- If we make morphisms representation-polymorphic, we must modify identity: + -- {-# MINIMAL (identity | identityU), compose #-} + -- identity :: (ρ ~ LRep) => mor a a + -- identity = force identityU + -- identityU :: Thunk (mor a a) + -- default identityU :: (ρ ~ LRep) => Thunk (mor a a) + -- identityU () = identity + identity :: mor a a + compose :: mor b c -> mor a b -> mor a c + +class (Category (DomOf f)) => Functor (f :: domObj -> Type ρ) where + type DomOf f :: domObj -> domObj -> TypeL + map :: DomOf f a b -> (f a -> f b) + +class (Category (LDomOf f)) => LFunctor (f :: lDomObj -> k -> Type ρ) where + type LDomOf f :: lDomObj -> lDomObj -> TypeL + + mapl :: LDomOf f a b -> (f a c -> f b c) + default mapl :: (Bifunctor f) => LDomOf f a b -> (f a c -> f b c) + mapl = flip bimap identity + +class (Category (RDomOf f)) => RFunctor (f :: k -> rDomObj -> Type ρ) where + type RDomOf f :: rDomObj -> rDomObj -> TypeL + + mapr :: RDomOf f a b -> (f c a -> f c b) + default mapr :: (Bifunctor f) => RDomOf f a b -> (f c a -> f c b) + mapr = bimap identity + +class (LFunctor f, RFunctor f, Category (Hask ρ)) => Bifunctor (f :: lDomObj -> rDomObj -> Type ρ) where + bimap :: LDomOf f a b -> RDomOf f c d -> (f a c -> f b d) + bimap f g = mapl f ∘ mapr g + +type Iso (mor :: obj -> obj -> TypeL) a b = (mor a b, mor b a) -- TODO: use strict pair + +class (Bifunctor f, LDomOf f ~ Hask ρ, RDomOf f ~ Hask ρ) => Monoidal (f :: Type ρ -> Type ρ -> Type ρ) where + type UnitOf f :: Type ρ + unitl :: Iso (Hask ρ) (f (UnitOf f) a) a + unitr :: Iso (Hask ρ) (f a (UnitOf f)) a + assoc :: Iso (Hask ρ) (f (f a b) c) (f a (f b c)) + +-- TODO: Having this only work for Hask sucks and is needlessly specialized. +-- We can generalize this and some above definitions to any category whose +-- objects have kind Type ρ, which isn't just Hask (e.g., Kleisli). +class (Monoidal f) => Cartesian (f :: Type ρ -> Type ρ -> Type ρ) where + fst :: forall (a :: Type ρ) (b :: Type ρ). f a b -> a + snd :: forall (a :: Type ρ) (b :: Type ρ). f a b -> b + + pair :: forall (a :: Type ρ) (b :: Type ρ) (c :: Type ρ). (a -> b) -> (a -> c) -> a -> f b c + pair f g = bimap f g ∘ diag + + diag :: forall (a :: Type ρ). a -> f a a + diag = pair identity identity + +type Hask ρ = (->) :: Type ρ -> Type ρ -> TypeL + +instance Category (Hask RepL) where + identity x = x + compose f g x = f (g x) + +(∘) :: (Category mor) => mor b c -> mor a b -> mor a c +(∘) = compose + +-} -- | Right-to-left composition. -- -- This operator is right associative to exploit laziness in the case where -- the outer function doesn't need its argument: -- --- > (const x << (f << g)) y ---> const x ((f << g) y) ---> x +-- > (const x <. (f <. g)) y ---> const x ((f <. g) y) ---> x -- --- Note that @f << g@ is never evaluated. -(<<) :: (Category v) => v b c -> v a b -> v a c -(<<) = (.) +-- Note that @f <. g@ is never evaluated. +-- (<.) :: (Category v) => v b c -> v a b -> v a c +-- (<.) = (.) -- | Left-to-right composition. -(>>) :: (Category v) => v a b -> v b c -> v a c -(>>) = flip (.) +-- +-- This operator is left associative to exploit laziness in the case where +-- the outer function doesn't need its argument: +-- +-- > ((g .> f) .> const x) y ---> const x ((g .> f) y) ---> x +-- +-- Note that @g .> f@ is never evaluated. +-- (.>) :: (Category v) => v a b -> v b c -> v a c +-- (.>) = flip (.) + +-- TODO: (.:), (<.:), (.:>) -------------------------------------------------------------------------------- -- Functor -infixl 0 ^&, &^ -infixl 1 $^, ^$ +{- +infixl 0 ^&, &^, ~^&, ^&~, ~&^, &^~ +infixl 1 $^, ^$, $^~, ~$^, ^$~, ~^$ infixr 2 <<^, ^>>, ^<<, >>^ -- | The action of a functor on a function. @@ -130,7 +431,7 @@ map = fmap -- | The action of a functor on a function, flipped. -- -- With the BlockArguments extension, expressions like "@for xs \\x -> ...@" --- look like imperative for-loops, hence the name "for". +-- look like for-loops, hence the name "for". for, (^&) :: (Functor f) => f a -> (a -> b) -> f b for = flip map (^&) = for @@ -145,31 +446,89 @@ flor, (&^) :: (Functor f) => a -> f (a -> b) -> f b flor = flip flap (&^) = flor +($^~) :: (Functor f, Functor g) => (a -> b) -> g (f a) -> g (f b) +f $^~ a = (f $^) $^ a + +(~^&) :: (Functor f, Functor g) => g (f a) -> (a -> b) -> g (f b) +(~^&) = flip ($^~) + +(~$^) :: (Functor f, Functor g) => g (a -> b) -> f a -> g (f b) +f ~$^ a = f ^& ($^ a) + +(^&~) :: (Functor f, Functor g) => f a -> g (a -> b) -> g (f b) +(^&~) = flip (~$^) + +(^$~) :: (Functor f, Functor g) => f (a -> b) -> g a -> g (f b) +f ^$~ a = (f ^$) $^ a + +(~&^) :: (Functor f, Functor g) => g a -> f (a -> b) -> g (f b) +(~&^) = flip (^$~) + +(~^$) :: (Functor f, Functor g) => g (f (a -> b)) -> a -> g (f b) +f ~^$ a = f ^& (^$ a) + +(&^~) :: (Functor f, Functor g) => a -> g (f (a -> b)) -> g (f b) +(&^~) = flip (~^$) + +-- TODO + -- | Right-to-left composition with the first function in a functor. (<<^) :: (Functor f, Category v) => v b c -> f (v a b) -> f (v a c) -g <<^ f = (g <<) $^ f +g <<^ f = (g <.) $^ f + +-- TODO -- | Left-to-right composition with the first function in a functor. (^>>) :: (Functor f, Category v) => f (v a b) -> v b c -> f (v a c) -f ^>> g = f ^& (>> g) +f ^>> g = f ^& (.> g) + +-- TODO -- | Right-to-left composition with the second function in a functor. (^<<) :: (Functor f, Category v) => f (v b c) -> v a b -> f (v a c) -g ^<< f = g ^& (<< f) +g ^<< f = g ^& (<. f) + +-- TODO -- | Left-to-right composition with the second function in a functor. (>>^) :: (Functor f, Category v) => v a b -> f (v b c) -> f (v a c) -f >>^ g = (f >>) $^ g +f >>^ g = (f .>) $^ g -- TODO: *^, ^* -------------------------------------------------------------------------------- -- Applicative -infixl 0 ^&^ -infixl 1 ^$^ +infixl 0 ^&^, ~^&^~ +infixl 1 ^$^, ~^$^~ infixr 2 ^<<^, ^>>^ +-- | Functions with applicative effects. +newtype Appy f a b = Appy {runAppy :: f (a -> b)} + +instance (Functor f) => Functor (Appy f a) where + fmap f (Appy a) = Appy (f $^~ a) + +instance (Applicative f) => Applicative (Appy f a) where + pure = Appy . pure . const + Appy f <*> Appy a = Appy (f ~^$^~ a) + +hom :: (a -> b) -> (b -> b -> c) -> a -> a -> c +hom f g x y = g (f x) (f y) + +instance (Alternative f) => Alternative (Appy f a) where + empty = Appy empty + +-- Appy x <|> Appy y = Appy (x <|> y) +-- (<|>) = hom runAppy (<|>) + +instance (Applicative f) => Category (Appy f) where + id = Appy (pure id) + Appy f . Appy g = Appy ((.) $^ f ^$^ g) + +-- TODO: Remove ap and plug? +-- TODO: liftA{,1,2,3,4} + -- | An alias for @pure ()@. pass :: (Applicative f) => f () pass = pure () @@ -185,13 +544,19 @@ plug, (^&^) :: (Applicative f) => f a -> f (a -> b) -> f b plug = (<**>) (^&^) = plug +(~^$^~) :: (Applicative f, Applicative g) => g (f (a -> b)) -> g (f a) -> g (f b) +f ~^$^~ a = (^$^) $^ f ^$^ a + +(~^&^~) :: (Applicative f, Applicative g) => g (f a) -> g (f (a -> b)) -> g (f b) +f ~^&^~ a = (^&^) $^ f ^$^ a + -- TODO: document (^<<^) :: (Applicative f, Category v) => f (v b c) -> f (v a b) -> f (v a c) -(^<<^) = liftA2 (<<) +(^<<^) = liftA2 (<.) -- TODO: document (^>>^) :: (Applicative f, Category v) => f (v a b) -> f (v b c) -> f (v a c) -(^>>^) = liftA2 (>>) +(^>>^) = liftA2 (.>) -- TODO: "semicolons" @@ -230,3 +595,4 @@ plug' mx mf = do f <- mf pure $! f x (^&!^) = plug' +-}