Могу ли я реализовать этот новый тип как композицию других типов?
Я написал новый тип Const3
это очень похоже на Const
, но содержит первый из трех заданных аргументов типа:
newtype Const3 a b c = Const3 { getConst3 :: a }
Я могу определить очень много полезных экземпляров для этого нового типа, но мне придется сделать все это самому.
Тем не менее, функция, которую я применяю на уровне типа, напоминает функцию
\a b c -> a
который @pl
говорит мне, эквивалентно const . const
,
И то и другое (.)
а также const
иметь соответствующие обертки нового типа: 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
и т.д. экземпляры из 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)
захватывая идею о том, что моноид является категорией одного объекта. Было бы неплохо, если бы было решение, которое все еще позволяет мне написать вышеупомянутый экземпляр.)
2 ответа
Из других ответов кажется, что это не так просто, однако, если вы хотите иметь только "бесплатные" экземпляры, одним из быстрых способов является использование newtype
сверх обычного Const
с GeneralizedNewtypeDeriving
расширение:
{-# 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)
То, что сбивает с толку - или, по крайней мере, то, что меня смутило - это то, что *
действует как конкретный тип, а не переменная типа. Так без 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 -> *