Могу ли я реализовать этот новый тип как состав других типов?
Я написал 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)