提升 - 泛化
Lifting - generalization
我需要使用繁重的功能提升,例如
k = myFunc
<$> someFunctionName 1
<*> someFunctionName 2
<*> someFunctionName 3
<*> someFunctionName 4
<*> someFunctionName 5
<*> someFunctionName 6
<*> someFunctionName 8
<*> someFunctionName 9
-- ...
Prelude 没有为更大的函数(大约 20 个参数)提供。有没有聪明的方法可以在不显式链接那些 ap
的情况下进行这样的提升?我正在寻找类似
的东西
k = magic (map someFunctionName [1,2,3,4,5,6,8,9]) myFunc
我可能很难猜出 magic
的类型,因为它取决于提升函数的参数数量。这里当然不能用map
on list(或者是?),我只是作为一个观点。
我想我正在寻找可以通过依赖类型很好地解决的问题,这些类型不包含在 Haskell 中,但也许有一些棘手的方法来解决它(模板 Haskell? )
你有什么想法可以让它更优雅、更灵活吗?
编辑: 在我的例子中,链接函数的类型都是一样的。
电梯施工人员
使用类型 类 我们可以定义 liftA
/ap
的通用版本。棘手的部分是推断何时停止提升和 return 结果。这里我们使用构造函数是柯里化函数,其参数与字段一样多,结果类型不是函数。
{-# 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
Higher-kinded 记录和仿制药
- Blog post on representing records as higher-kinded data
- generics-sop documentation
- Gist of this answer
另一种解决方案是首先通过包装每个字段的类型函数来参数化记录,这样我们就可以放置各种其他相关类型的东西。这些允许我们通过使用 Haskell 泛型遍历那些派生结构来生成和使用实际记录。
data UserF f = User
{ name :: f @@ String
, age :: f @@ Int
} deriving G.Generic
type User = UserF Id
类型函数是使用类型族 (@@)
定义的(上面链接的博客 post 中的 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)
),我们可以用 [=24= 解析 CSV 记录 (r :: [String]
) ] 以下。这里使用 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
让我们分解代码bottom-up。 generics-sop 提供组合器以统一的方式转换记录,就像使用列表一样。最好遵循适当的教程来了解底层细节,但为了演示,我们假设 from
和 to
之间的管道中间实际上是在转换列表,使用动态输入 Field
以输入异构列表。
from
将记录转换为异构字段列表,但由于它们都是 Int
,因此列表现在确实是同质的 from :: p (Cn Int) -> [Int]
。
这里使用 (!!)
和 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 }
我需要使用繁重的功能提升,例如
k = myFunc
<$> someFunctionName 1
<*> someFunctionName 2
<*> someFunctionName 3
<*> someFunctionName 4
<*> someFunctionName 5
<*> someFunctionName 6
<*> someFunctionName 8
<*> someFunctionName 9
-- ...
Prelude 没有为更大的函数(大约 20 个参数)提供。有没有聪明的方法可以在不显式链接那些 ap
的情况下进行这样的提升?我正在寻找类似
k = magic (map someFunctionName [1,2,3,4,5,6,8,9]) myFunc
我可能很难猜出 magic
的类型,因为它取决于提升函数的参数数量。这里当然不能用map
on list(或者是?),我只是作为一个观点。
我想我正在寻找可以通过依赖类型很好地解决的问题,这些类型不包含在 Haskell 中,但也许有一些棘手的方法来解决它(模板 Haskell? )
你有什么想法可以让它更优雅、更灵活吗?
编辑: 在我的例子中,链接函数的类型都是一样的。
电梯施工人员
使用类型 类 我们可以定义 liftA
/ap
的通用版本。棘手的部分是推断何时停止提升和 return 结果。这里我们使用构造函数是柯里化函数,其参数与字段一样多,结果类型不是函数。
{-# 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
Higher-kinded 记录和仿制药
- Blog post on representing records as higher-kinded data
- generics-sop documentation
- Gist of this answer
另一种解决方案是首先通过包装每个字段的类型函数来参数化记录,这样我们就可以放置各种其他相关类型的东西。这些允许我们通过使用 Haskell 泛型遍历那些派生结构来生成和使用实际记录。
data UserF f = User
{ name :: f @@ String
, age :: f @@ Int
} deriving G.Generic
type User = UserF Id
类型函数是使用类型族 (@@)
定义的(上面链接的博客 post 中的 HKD
)。与此答案相关的是恒等函数和常量函数。
type family s @@ x
type instance Id @@ x = x
type instance Cn a @@ x = a
data Id
data Cn (a :: *)
例如,我们可以在 UserF (Cn Int)
:
userIxes = User { name = 0, age = 2 } :: UserF (Cn Int)
给定这样的参数化记录类型 (p = UserF
) 和索引记录 (ixes :: p (Cn Int)
),我们可以用 [=24= 解析 CSV 记录 (r :: [String]
) ] 以下。这里使用 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
让我们分解代码bottom-up。 generics-sop 提供组合器以统一的方式转换记录,就像使用列表一样。最好遵循适当的教程来了解底层细节,但为了演示,我们假设 from
和 to
之间的管道中间实际上是在转换列表,使用动态输入 Field
以输入异构列表。
from
将记录转换为异构字段列表,但由于它们都是Int
,因此列表现在确实是同质的from :: p (Cn Int) -> [Int]
。这里使用
(!!)
和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 }