提升 - 概括

时间:2018-04-03 11:18:33

标签: haskell lifting

我需要使用重型功能提升,例如

k = myFunc
  <$> someFunctionName 1
  <*> someFunctionName 2
  <*> someFunctionName 3
  <*> someFunctionName 4
  <*> someFunctionName 5
  <*> someFunctionName 6
  <*> someFunctionName 8
  <*> someFunctionName 9
  -- ...

Prelude没有为更大的功能(大约20个参数)提供。如果没有明确地链接那些ap s,有没有聪明的方法来做这样的提升?我正在寻找像

这样的东西
k = magic (map someFunctionName [1,2,3,4,5,6,8,9]) myFunc 

我可能很难猜测magic的类型,因为它取决于提升函数的参数数量。当然不可能在列表中使用map(或者是吗?),我只把它作为一个观点。

我认为我正在寻找可以通过依赖类型很好地解决的问题,这些类型不包含在Haskell中,但可能有一些棘手的方法来解决它(TemplateHaskell?)

您是否有任何想法如何使其更优雅和灵活?

编辑:在我看来,链式函数的类型都是一样的。

1 个答案:

答案 0 :(得分:4)

提升构造函数

使用类型类,我们可以定义liftA / ap的通用版本。棘手的部分是推断何时停止提升并返回结果。在这里,我们使用这样的事实:构造函数是curried函数,其参数与字段一样多,而结果类型不是函数。

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

import Text.Read

-- apF
--   :: Applicative f
--   => (i -> f a)
--   -> (a -> a -> ... ->   x)   -- constructor type
--   -> (i -> i -> ... -> f x)   -- lifted function
class Applicative f => ApF f i a s t where
  apF :: (i -> f a) -> f s -> t

-- Recursive case
-- s ~ (a -> ...)
-- t ~ (i -> ...)
instance (a ~ a', t ~ (i -> t'), ApF f i a s' t') => ApF f i a (a' -> s') t where
  apF parseArg fconstr i = apF parseArg (fconstr <*> parseArg i)

-- Base case
-- s ~   x  -- x assumed not to be a function type (not (y -> z) for any y and z)
-- t ~ f x
instance {-# OVERLAPPABLE #-} (t ~ f x, Applicative f) => ApF f i a x t where
  apF _ fconstr = fconstr

liftF :: ApF f i a s t => (i -> f a) -> s -> t
liftF parseArg constr = apF parseArg (pure constr)

main = do
  let lookup :: Int -> Maybe Integer
      lookup i =
        case drop i [2,3,5,7,11,13] of
          [] -> Nothing
          a : _ -> Just a
  print $ liftF lookup (,,) 0 2 5

高级记录和泛型

另一种解决方案是首先通过包装每个字段的类型函数来参数化记录,以便我们可以放置各种其他相关类型的东西。这些允许我们通过使用Haskell Generics遍历那些派生结构来生成和使用实际记录。

data UserF f = User
  { name :: f @@ String
  , age :: f @@ Int
  } deriving G.Generic

type User = UserF Id

类型函数是使用类型系列(@@)(上面链接的博客文章中的HKD)定义的。与这个答案相关的是身份和常数函数。

type family s @@ x
type instance Id   @@ x = x
type instance Cn a @@ x = a

data Id
data Cn (a :: *)

例如,我们可以在UserF (Cn Int)

中收集用于解析CSV的索引
userIxes = User { name = 0, age = 2 } :: UserF (Cn Int)

鉴于此类参数化记录类型(p = UserF)和索引记录(ixes :: p (Cn Int)),我们可以使用下面的r :: [String]解析CSV记录(parseRec) 。这里使用generics-sop

parseRec :: _
         => p (Cn Int) -> [String] -> Maybe (p Id)
parseRec ixes r =
  fmap to .
  hsequence .
  htrans (Proxy :: Proxy ParseFrom) (\(I i) -> read (r !! i)) .
  from $
  ixes

让我们自下而上地分解代码。 generics-sop 提供组合器以统一的方式转换记录,就像使用列表一样。最好按照正确的教程来理解底层细节,但为了演示,我们可以想象fromto之间的管道中间实际上是使用动态转换列表键入Field以输入异构列表。

  • from将记录转换为异构的字段列表,但由于它们全部为Int,因此列表现在非常均匀from :: p (Cn Int) -> [Int]

    < / LI>
  • 此处使用(!!)read,我们使用给定的索引i获取并解析每个字段。 htrans Proxy基本上是map(Int -> Maybe Field) -> [Int] -> [Maybe Field]

  • hsequence基本上是sequence :: [Maybe Field] -> Maybe [Field]

  • to将字段列表转换为具有兼容字段类型[Field] -> p Id的记录。

最后一步是毫不费力的:

parseUser :: Record -> Maybe User
parseUser = parseRec $ User { name = 0, age = 2 }