haskell - 我想编写一个类似于Haskell中的`flip`的函数来摆脱lambda表达式。但我不能处理它的类型

标签 haskell lambda polymorphism typeclass type-systems

我想编写一个Haskell函数,其功能类似于flip,但更为通用,可以使函数的任何参数成为最后一个参数。为了方便起见,我们使用pull表示它。

编写以下代码很容易:

Prelude> :t flip          --we just call this function a swap
flip :: (a -> b -> c) -> b -> a -> c
Prelude> :t (flip.)       --we just call this function a swap
(flip.) :: (a -> a1 -> b -> c) -> a -> b -> a1 -> c
Prelude> :t ((flip.).)    --we just call this function a swap
((flip.).) :: (a -> a1 -> a2 -> b -> c) -> a -> a1 -> b -> a2 -> c
Prelude> :t (((flip.).).) --we just call this function a swap
(((flip.).).)
  :: (a -> a1 -> a2 -> a3 -> b -> c) -> a -> a1 -> a2 -> b -> a3 -> c


而且我们发现,将更多(。)应用于翻转,它可以交换任意相邻的参数对。根据以上结果,我们可以编写:

Prelude> :t flip
flip :: (a -> b -> c) -> b -> a -> c
Prelude> :t (flip.) . flip
(flip.) . flip :: (a1 -> a -> b -> c) -> a -> b -> a1 -> c
Prelude> :t ((flip.).) . (flip.) . flip
((flip.).) . (flip.) . flip
  :: (a2 -> a -> a1 -> b -> c) -> a -> a1 -> b -> a2 -> c
Prelude> :t (((flip.).).) . ((flip.).) . (flip.) . flip
(((flip.).).) . ((flip.).) . (flip.) . flip
  :: (a3 -> a -> a1 -> a2 -> b -> c) -> a -> a1 -> a2 -> b -> a3 -> c


我们可以发现,通过组成更多的交换,它可以将任意参数拉到最后一个位置。因此,在许多情况下,我们都可以摆脱lambda表达式。但是以上快递很very肿。

我的主要思想是制作一个函数pull来概括上述功能。 pull行为
像波纹管一样

let f = undefined               --For convenience, we let f be undefined.

:t pull 0 (f::a->b->z)          --the type variable z is not a function type.
>pull 0 (f::a->b->z) :: b->a->z --pull is just like flip for 0 and a function of this type.
:t pull 0 (f::a->b->c->z)       --the type variable z is not a function type.
>pull 0 (f::a->b->c->z) :: b->c->a->z

:t pull 1 (f::a->b->c->z)       --the type variable z is not a function type.
>pull 1 (f::a->b->c->z) :: a->c->b->z
:t pull 1 (f::a->b->c->d->z)    --the type variable z is not a function type.
>pull 1 (f::a->b->c->d->z) :: a->c->d->b->z

:t pull 2 (f::a->b->c->d->z)    --the type variable z is not a function type.
>pull 2 (f::a->b->c->d->z) :: a->b->d->c->z
:t pull 2 (f::a->b->c->d->e->z) --the type variable z is not a function type.
>pull 2 (f::a->b->c->d->e->z) :: a->b->d->e->c->z


我尝试了许多方法来做到这一点。最幼稚的是:

swap :: Word -> a -> a
swap 0 = flip
swap n = dot $ swap (n-1)


和ghc像波纹管一样抱怨,我明白原因:

-- Prelude> :reload
-- [1 of 1] Compiling Main             ( ModifyArbitrayParameterOfAFunction.hs, interpreted )
--
-- ModifyArbitrayParameterOfAFunction.hs:4:21:
--     Occurs check: cannot construct the infinite type: c0 = a1 -> c0
--     Expected type: (a1 -> c0) -> c0
--       Actual type: (a1 -> c0) -> a1 -> c0
--     In the return type of a call of `modify'
--     Probable cause: `modify' is applied to too few arguments
--     In the first argument of `(.)', namely `(modify (n - 1) modi)'
--     In the expression: (modify (n - 1) modi) . f1
--
-- ModifyArbitrayParameterOfAFunction.hs:4:42:
--     Occurs check: cannot construct the infinite type: c0 = a1 -> c0
--     Expected type: a1 -> a1 -> c0
--       Actual type: a1 -> c0
--     In the second argument of `(.)', namely `f1'
--     In the expression: (modify (n - 1) modi) . f1
--     In an equation for `modify':
--         modify n modi f1 = (modify (n - 1) modi) . f1
-- Failed, modules loaded: none.


也许我的目标只是一厢情愿,但考虑到Haskell的类型系统甚至能够编写lambda表达式,我敢说必须有一种方法可以做到这一点。

最佳答案

如注释中所述,您无法将要翻转的函数的属性传递给该函数。参数是在运行时计算的,而您在编译时需要该值,以便可以确定正确的类型。

如果不以某种方式通过Arity,就无法做到。例如,可以将a -> b -> c -> d视为三个返回d的参数的函数,或者如果两个参数返回c -> d的函数。

可能最简单的解决方案是显式定义函数,例如flip2flip3等。我知道这不是您想要的,但这是最实用的解决方案。

另一种选择是使用模板Haskell。然后,情况就不同了,因为Template Haskell在编译时执行(我说“ meta-”)代码。使用TH,您可以创建一个采用自然数并生成TH表达式的函数,该表达式可以编译到另一个模块中。元功能可以定义为

{-# LANGUAGE TemplateHaskell #-}
module GenFlipTH where

import Language.Haskell.TH

pull :: Int -> Q Exp
pull 0              = varE 'flip
pull n | n < 0      = fail "Negative arity"
       | otherwise  = [| fmap $(pull (n - 1)) . flip |]
-- Here we use the fact that `(->) r` is a functor.


并在另一个模块中用于生成适当的表达式,例如

{-# LANGUAGE TemplateHaskell #-}
import GenFlipTH

flip3 :: (a -> b3 -> b2 -> b1 -> b -> c) -> (b3 -> b2 -> b1 -> b -> a -> c)
flip3 = $( pull 3 )


这可能接近我所能满足的要求-您可以通过数字确定函数并获得正确创建和使用它的编译时保证。

关于haskell - 我想编写一个类似于Haskell中的`flip`的函数来摆脱lambda表达式。但我不能处理它的类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17145940/

相关文章:

haskell - 显示 IO 类型

haskell - 使用 Cabal 升级包依赖

c# - 我可以在一行代码中读取一个数组吗?

C++ Lambda 函数闭包 - 内存问题

java - 使用可选对象字段返回空对象或新对象

c# - 如何在 C# 中以更通用的方式编写这些方法?

c++ - 创建具有多态性和 op 的模板。 C++中的重载

haskell - Haskell 中的 ((+) . (+)) 是什么?

Haskell 函数组合题

mysql - 将 PDO FETCH_CLASS 与多态类一起使用