heftia-0.1.0.0: src/Data/Free/Extensible.hs
{-# LANGUAGE UndecidableInstances #-}
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
{- |
Copyright : (c) 2023 Yamada Ryo
License : MPL-2.0 (see the file LICENSE)
Maintainer : ymdfield@outlook.jp
Stability : experimental
Portability : portable
An implementation of an open union for first-order effects using
the [extensible](https://hackage.haskell.org/package/extensible) package as a backend.
-}
module Data.Free.Extensible where
import Control.Effect.Class (Instruction)
import Data.Extensible (Forall, Match (Match), htabulateFor, leadership, match)
import Data.Extensible.Sum (exhaust, strikeAt, (<:|), type (:/) (EmbedAt))
import Data.Free.Union (
Union (
HasMembership,
absurdUnion,
inject,
inject0,
project,
weaken,
(|+|:)
),
)
import Data.Proxy (Proxy (Proxy))
import GHC.TypeNats (KnownNat, Nat, natVal, type (+))
import Type.Membership (Membership, nextMembership)
import Unsafe.Coerce (unsafeCoerce)
{- |
An implementation of an open union for first-order effects using
the [extensible](https://hackage.haskell.org/package/extensible) package as a backend.
-}
newtype ExtensibleUnion fs a = ExtensibleUnion {unExtensibleUnion :: fs :/ FieldApp a}
newtype FieldApp a (f :: Instruction) = FieldApp {unFieldApp :: f a}
instance Forall Functor fs => Functor (ExtensibleUnion fs) where
fmap f =
ExtensibleUnion
. match
( htabulateFor @Functor Proxy \w ->
Match \e -> EmbedAt w $ FieldApp $ f <$> unFieldApp e
)
. unExtensibleUnion
{-# INLINE fmap #-}
{- todo:
instance Forall Foldable fs => Foldable (ExtensibleUnion fs) where
instance Forall Traversable fs => Traversable (ExtensibleUnion fs) where
-}
instance Union ExtensibleUnion where
type HasMembership _ f fs = KnownNat (TypeIndex fs f)
inject = ExtensibleUnion . EmbedAt findFirstMembership . FieldApp
{-# INLINE inject #-}
project (ExtensibleUnion u) = unFieldApp <$> strikeAt findFirstMembership u
{-# INLINE project #-}
absurdUnion = exhaust . unExtensibleUnion
{-# INLINE absurdUnion #-}
inject0 = ExtensibleUnion . EmbedAt leadership . FieldApp
{-# INLINE inject0 #-}
weaken (ExtensibleUnion (EmbedAt w e)) =
ExtensibleUnion $ EmbedAt (nextMembership w) e
{-# INLINE weaken #-}
f |+|: g = (f . unFieldApp <:| g . ExtensibleUnion) . unExtensibleUnion
{-# INLINE (|+|:) #-}
findFirstMembership :: forall xs x. KnownNat (TypeIndex xs x) => Membership xs x
findFirstMembership = unsafeMkMembership $ fromIntegral $ natVal @(TypeIndex xs x) Proxy
where
-- This hack may break if the membership package version gets updated.
unsafeMkMembership :: Int -> Membership xs x
unsafeMkMembership = unsafeCoerce
type family TypeIndex (xs :: [k]) (x :: k) :: Nat where
TypeIndex (x ': xs) x = 0
TypeIndex (y ': xs) x = 1 + TypeIndex xs x