Могу ли я реализовать этот новый тип как состав других типов?

Я написал newtype Const3, который очень похож на Const, но содержит первый из трех заданных аргументов типа:

newtype Const3 a b c = Const3 { getConst3 :: a }

Я могу определить очень много полезных экземпляров для этого нового типа, но я должен был бы все это сделать сам.

Однако функция, которую я применяю на уровне типа, похожа на функцию

\a b c -> a

который @pl говорит мне, эквивалентен const . const.

Оба (.) и Const имеют соответствующие оболочки newtype: Compose и Const. Поэтому я решил, что смогу написать:

type Const3 = Compose Const Const

И наследуйте полезные экземпляры автоматически, например:

instance Functor (Const m)
instance (Functor f, Functor g) => Functor (Compose f g)
-- a free Functor instance for Const3!

Но GHC не согласен:

const3.hs:5:23:
    Expecting one more argument to ‘Const’
    The first argument of ‘Compose’ should have kind ‘* -> *’,
      but ‘Const’ has kind ‘* -> * -> *’
    In the type ‘Compose Const Const’
    In the type declaration for ‘Const3’

Это, по-видимому, связано с типами Compose и Const:

*Main> :k Compose
Compose :: (* -> *) -> (* -> *) -> * -> *
*Main> :k Const
Const :: * -> * -> *

Итак, после небольшого поиска я обнаружил, что расширение GHC под названием PolyKinds, которое позволяет мне делать что-то вроде:

{-# LANGUAGE PolyKinds #-}
newtype Compose f g a = Compose { getCompose :: f (g a) }
newtype Const a b = Const { getConst :: a }

И, как по волшебству, все правильно:

 *Main> :k Compose
 Compose :: (k -> *) -> (k1 -> k) -> k1 -> *
 *Main> :k Const
 Const :: * -> k -> *

Но я все еще не могу написать им писать Const3 = Compose Const Const.

const3.hs:12:23:
    Expecting one more argument to ‘Const’
    The first argument of ‘Compose’ should have kind ‘* -> *’,
      but ‘Const’ has kind ‘* -> k0 -> *’
    In the type ‘Compose Const Const’
    In the type declaration for ‘Const3’

Что дает? Есть ли какой-нибудь умный способ сделать это, поэтому я могу воспользоваться преимуществами наследования экземпляров Functor etc от Const и Compose?

(Как примечание, оригинальная мысль, которая привела меня к Const3, писала:

newtype Const3 a b c = Const3 { getConst3 :: a }

instance Monoid m => Category (Const3 m) where
  id = Const3 mempty
  Const3 x . Const3 y = Const3 (mappend x y)

захват идеи о том, что моноид - это категория одного объекта. Было бы неплохо, если бы было решение, которое все еще позволяет мне как-то написать вышеприведенный экземпляр.)

Ответ 1

То, что сбивает с толку - или, по крайней мере, то, что меня смутило, - это то, что * действует как конкретный тип, а не переменная типа. Таким образом, без PolyKinds, Compose имеет тип, который больше похож:

compose :: (A -> A) -> (A -> A) -> A -> A

Реально, мы не можем заменить A на A -> A, потому что они будут разными типами, поэтому по той же логике мы не можем заменить * на * -> *.

Даже при PolyKinds типы все еще не правильны. В частности, Compose ожидает (k -> *) в качестве своего первого аргумента, и вы пытаетесь дать ему (k -> (k2 -> *)).

Причина, по которой вы вынуждены возвращать тип *, заключается в том, что вы используете newtypes, а newtypes должны возвращать конкретный тип (т.е. типа *). Я попытался преодолеть это, превратив Compose в синоним типа, который, наконец, имел именно тот вид, который мы хотим (с PolyKinds):

type Compose f g a = (f (g a))

λ> :k Compose
Compose :: (k1 -> k) -> (k2 -> k1) -> k2 -> k

Однако, использование этого еще дало мне аналогичную ошибку, и я не уверен, что мы сможем заставить его работать правильно. Проблема возникла из-за того, что применение Compose к первому Const дает нам вид с * в нем, вероятно, из-за ограничений псевдонимов типов, подобных этому:

λ> :k Compose Const
Compose Const :: (k -> *) -> k -> k1 -> *

Ответ 2

Из других ответов кажется, что это не так просто, однако, если единственное, что вы хотите иметь, это "свободные" экземпляры, один быстрый способ использует newtype поверх обычного Const с помощью GeneralizedNewtypeDeriving extension:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE PatternSynonyms #-}
module ConstThree (Const3,pattern Const3,getConst3) where
import Data.Foldable
import Data.Traversable
import Control.Applicative
import Data.Monoid

newtype Const3 a b c = MkConst3 (Const a c) deriving (Functor,Applicative,Foldable,Traversable,Eq,Ord,Show,Monoid)

pattern Const3 :: a -> Const3 a b c
pattern Const3 x = MkConst3 (Const x)

getConst3 :: Const3 a b c -> a
getConst3 (Const3 x) = x

В приведенном выше примере я также использую PatternSynonyms, чтобы скрыть внутреннее использование Const от клиентов.

Это то, что вы получаете:

λ> :t Const3
Const3 :: a -> Const3 a b c
λ> :t getConst3
getConst3 :: Const3 a b c -> a
λ> :i Const3
pattern Const3 :: a -> Const3 a b c
        -- Defined at /tmp/alpha-dbcdf.hs:13:5

type role Const3 representational phantom phantom
newtype Const3 a b c = MkConst3 (Const a c)
        -- Defined at /tmp/alpha-dbcdf.hs:10:5
instance Eq a => Eq (Const3 a b c)
  -- Defined at /tmp/alpha-dbcdf.hs:10:100
instance Functor (Const3 a b)
  -- Defined at /tmp/alpha-dbcdf.hs:10:59
instance Ord a => Ord (Const3 a b c)
  -- Defined at /tmp/alpha-dbcdf.hs:10:103
instance Show a => Show (Const3 a b c)
  -- Defined at /tmp/alpha-dbcdf.hs:10:107
instance Monoid a => Applicative (Const3 a b)
  -- Defined at /tmp/alpha-dbcdf.hs:10:67
instance Foldable (Const3 a b)
  -- Defined at /tmp/alpha-dbcdf.hs:10:79
instance Traversable (Const3 a b)
  -- Defined at /tmp/alpha-dbcdf.hs:10:88
instance Monoid a => Monoid (Const3 a b c)
  -- Defined at /tmp/alpha-dbcdf.hs:10:112

И как и ожидалось, вы можете сделать:

instance Monoid m => Category (Const3 m) where
  id = Const3 mempty
  Const3 x . Const3 y = Const3 (mappend x y)