简体   繁体   English

您可以在 Haskell 类型签名中编写参数化类型吗?

[英]Can you compose parameterised types in Haskell type signatures?

I've been trying to write a custom Optics data structure that generalises Lenses, Prisms and Traversals.我一直在尝试编写一个自定义光学数据结构来概括镜头、棱镜和遍历。 My data structure looks like this:我的数据结构如下所示:

data Optic m a b = Optic { view :: a -> m b
                         , over :: a -> (b -> b) -> a
                         }

I want to write a function that composes two Optics, optic1:: Optic mab and optic2:: Optic nb c to produce an Optic containing view:: a -> m (nc) and over:: a -> (c -> c) -> a .我想写一个 function 组成两个光学元件, optic1:: Optic maboptic2:: Optic nb c以生成包含view:: a -> m (nc)over:: a -> (c -> c) -> a

In my head, the type of this composed Optic would be Optic (mn) a c , but this doesn't work - GHC will complain that m has one too many type arguments and n one too few.在我的脑海中,这种组合光学的类型将是Optic (mn) a c ,但这不起作用 - GHC 会抱怨 m 有太多类型 arguments 和 n 太少。

Here's my non-compiling implementation of the compose function:这是我对 compose function 的非编译实现:

compose :: Optic m a b -> Optic n b c -> (m b -> (b -> n c) -> m (n c)) -> Optic (m n) a c
compose optic1 optic2 glue = Optic { view = viewCompose (view optic1) (view optic2) glue
                                   , over = overCompose (over optic1) (over optic2)
                                   }

viewCompose :: (a -> m b) -> (b -> n c) -> (m b -> (b -> n c) -> m (n c)) -> a -> m (n c)
viewCompose view1 view2 glue x = glue (view1 x) view2

overCompose :: (a -> (b -> b) -> a) -> (b -> (c -> c) -> b) -> a -> (c -> c) -> a
overCompose over1 over2 x f = over1 x (\y -> over2 y f)

The GHC error messages are: GHC 错误消息是:

optic.hs:7:83: error:
    • Expecting one fewer argument to ‘m n’
      Expected kind ‘* -> *’, but ‘m n’ has kind ‘*’
    • In the first argument of ‘Optic’, namely ‘m n’
      In the type signature:
        compose :: Optic m a b
                   -> Optic n b c -> (m b -> (b -> n c) -> m (n c)) -> Optic (m n) a c

optic.hs:7:85: error:
    • Expecting one more argument to ‘n’
      Expected a type, but ‘n’ has kind ‘* -> *’
    • In the first argument of ‘m’, namely ‘n’
      In the first argument of ‘Optic’, namely ‘m n’
      In the type signature:
        compose :: Optic m a b
                   -> Optic n b c -> (m b -> (b -> n c) -> m (n c)) -> Optic (m n) a c

If I create an optic of type Optic Maybe Int Int , GHC understands that the first type argument has kind * -> * and doesn't complain about insufficient arguments.如果我创建一个Optic Maybe Int Int类型的光学元件,GHC 会理解第一个类型参数有 kind * -> *并且不会抱怨 arguments 不足。 But I can't figure out how to combine types together to create another type of kind * -> * .但我不知道如何将类型组合在一起以创建另一种类型* -> *

Is there any way (with or without language extensions) to express something like:有什么方法(有或没有语言扩展)来表达类似的东西:

Optic (forall t. m (n t)) a c

As per @chi's comment, Haskell doesn't directly support type-level lambdas.根据@chi 的评论,Haskell 不直接支持类型级 lambda。 So, while there exists a type named Maybe of kind * -> * which directly represents the type-level lambda \a ~> Maybe a , there's no corresponding type directly representing the type-level lambda \a ~> Maybe (Maybe a) .因此,虽然存在一个名为Maybe的类型* -> *直接表示类型级别 lambda \a ~> Maybe a ,但没有对应的类型直接表示类型级别 lambda \a ~> Maybe (Maybe a) .

This means that given your defined type for the field view :这意味着给定您为字段view定义的类型:

view :: a -> m b

it is impossible to find an optic Optic mab for any type m that would satisfy:不可能为任何类型的m找到满足以下条件的 optic Optic mab

view :: a -> Maybe (Maybe b)  -- impossible

You must instead use some kind of encoding for these types.您必须改为对这些类型使用某种编码。 The Compose newtype imported from Data.Functor.Compose is one alternative.Data.Functor.Compose导入的Compose新类型是一种替代方法。 It's definition is:它的定义是:

newtype Compose m n a = Compose (m (n a))

It basically wraps up the type lambda \a ~> m (na) which has no direct Haskell representation into a type lambda \a ~> (Compose mn) a whose direct Haskell representation is simply Compose mn: * -> * . It basically wraps up the type lambda \a ~> m (na) which has no direct Haskell representation into a type lambda \a ~> (Compose mn) a whose direct Haskell representation is simply Compose mn: * -> * .

The drawback is that it will introduce a non-uniformity in your types -- there'll be "plain" optics like Optic Maybe Int Int and then "composed" optics, like Optic (Compose Maybe Maybe) Int Int .缺点是它会在您的类型中引入不均匀性——将有“普通”光学元件,如Optic Maybe Int Int ,然后是“组合”光学元件,如Optic (Compose Maybe Maybe) Int Int You can use coerce to work around this inconvenience in most cases.在大多数情况下,您可以使用coerce来解决这种不便。

The appropriate definition of compose using the Compose newtype would look something like:使用Compose新类型的compose的适当定义如下所示:

type Glue m n b c = m b -> (b -> n c) -> m (n c)

compose :: Optic m a b -> Optic n b c -> Glue m n b c -> Optic (Compose m n) a c
compose optic1 optic2 glue
  = Optic { view = viewCompose (view optic1) (view optic2) glue
          , over = overCompose (over optic1) (over optic2)
          }
  where
    viewCompose view1 view2 glue x = Compose $ glue (view1 x) view2
    overCompose over1 over2 x f = over1 x (\y -> over2 y f)

and for a typical Maybe -based optic:对于典型的基于Maybe的光学元件:

_Left :: Optic Maybe (Either a b) a
_Left = Optic v o
  where v (Left x) = Just x
        v (Right _) = Nothing
        o (Left x) f = Left (f x)
        o (Right y) _ = Right y

a composed optic might look like:一个组合的光学可能看起来像:

_Left2 = compose _Left _Left (flip fmap)

Using it directly will introduce a Compose wrapper:直接使用它会引入一个Compose包装器:

> view _Left2 (Left (Left "xxx"))
Compose (Just (Just "xxx"))

but you can coerce the result to avoid explicit unwrapping, particularly helpful if there are multiple nested Compose layers:但是您可以coerce结果以避免显式展开,如果有多个嵌套的Compose层特别有用:

λ> import Data.Coerce
λ> _Left4 = compose _Left2 _Left2 (flip fmap)
λ> :t _Left4
_Left4
  :: Optic
       (Compose (Compose Maybe Maybe) (Compose Maybe Maybe))
       (Either (Either (Either (Either c b4) b5) b6) b7)
       c
λ> view _Left4 (Left (Left (Left (Left True))))
Compose (Compose (Just (Just (Compose (Just (Just True))))))
λ> coerce $ view _Left4 (Left (Left (Left (Left True)))) :: Maybe (Maybe (Maybe (Maybe Bool)))
Just (Just (Just (Just True)))

The full code:完整代码:

import Data.Coerce
import Data.Functor.Compose

data Optic m a b = Optic { view :: a -> m b
                         , over :: a -> (b -> b) -> a
                         }

type Glue m n b c = m b -> (b -> n c) -> m (n c)

compose :: Optic m a b -> Optic n b c -> Glue m n b c -> Optic (Compose m n) a c
compose optic1 optic2 glue
  = Optic { view = viewCompose (view optic1) (view optic2) glue
          , over = overCompose (over optic1) (over optic2)
          }
  where
    viewCompose view1 view2 glue x = Compose $ glue (view1 x) view2
    overCompose over1 over2 x f = over1 x (\y -> over2 y f)

_Left :: Optic Maybe (Either a b) a
_Left = Optic v o
  where v (Left x) = Just x
        v (Right _) = Nothing
        o (Left x) f = Left (f x)
        o (Right y) _ = Right y

_Left2 :: Optic (Compose Maybe Maybe) (Either (Either c b1) b2) c
_Left2 = compose _Left _Left (flip fmap)

_Left4 :: Optic (Compose (Compose Maybe Maybe) (Compose Maybe Maybe)) (Either (Either (Either (Either c b1) b2) b3) b4) c
_Left4 = compose _Left2 _Left2 (flip fmap)

main = do
  print $ view _Left4 (Left (Left (Left (Left True))))
  print $ (coerce $ view _Left4 (Left (Left (Left (Left True)))) :: Maybe (Maybe (Maybe (Maybe Bool))))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM