柯里化 Haskell 与仿函数和函数组合
Currying with Haskell with functor and function composition
我有以下问题。我有一个功能很好用。
getName :: Style -> Css -> Maybe String
getName s css = map intToChar . intToBase 26 <$> getIndex s css
我想知道为什么我不能这样定义它:
getName :: Style -> Css -> Maybe String
getName = map intToChar . intToBase 26 <$> getIndex
我认为柯里化可以让我return作为函数接受两个参数而不是一个有两个参数的函数
如果对您的理解有帮助:
intToChar :: Int -> Char
intToBase :: Int -> Int -> [Int]
getIndex :: Style > Css -> Maybe Int
啊,不要忘记错误:
error:
• Couldn't match type ‘[a] -> Maybe Int’ with ‘Int’
Expected type: a -> Int
Actual type: a -> [a] -> Maybe Int
• Probable cause: ‘getIndex’ is applied to too few arguments
In the second argument of ‘(<$>)’, namely ‘getIndex’
In the expression: map intToChar . intToBase 26 <$> getIndex
In an equation for ‘getName’:
getName = map intToChar . intToBase 26 <$> getIndex
• Relevant bindings include
getName :: a -> [Char] (bound at src/Css/Css.hs:17:1)
|
17 | getName = map intToChar . intToBase 26 <$> getIndex
| ^^^^^^^^
问题是 s
和 css
不能通过 eta conversion 消除,因为它们单独用作 getIndex
的参数,而不是 map intToChar . intToBase 26 <$> getIndex
.
要走point-free,就得用函数组合。首先,使用 fmap
而不是 <$>
重写您的定义(并将 fmap
的不相关参数隐藏在局部变量后面)。
getName s css = fmap f (getIndex s css)
where f = map intToChar . intToBase 26
现在更容易看出需要用函数组合先消去css
:
-- f1 (f2 x) == f1 . f2
-- f1 = fmap f
-- f2 = getIndex s
getName s = fmap f . (getIndex s)
where f = map intToChar . intToBase 26
然后 s
:
-- f1 (f2 x) = f1 . f2
-- f1 = (fmap f .)
-- f2 = getIndex
getName = (fmap f . ) . getIndex
where f = map intToChar . intToBase 26
也许需要加上括号。首先,您尝试应用的原则是有效的。也就是说,如果一个变量是函数的最后一个参数并且是函数中 top-level 调用的最后一个参数,则可以有效地“right-cancel”这两个变量,所以如果你有
prependMy x = mappend "my" x
然后你可以安全地“取消”x
并得到等效的(模单态限制,这里不适用但有时会咬你)
prependMy = mappend "my"
现在,这是你的表情。
getName s css = map intToChar . intToBase 26 <$> getIndex s css
为了清楚起见,现在相同的东西加上一些括号
getName s css = (map intToChar . intToBase 26) <$> (getIndex s css)
现在 css
不在 top-level 上了。以前不是,现在更清楚不是了。如果您可以重新排列表达式,使 s
和 css
实际上位于 top-level 调用处,那么您仍然可以取消。在这种情况下,您可以通过执行以下操作来摆脱 css
。
getName s css = (map intToChar . intToBase 26) <$> (getIndex s css)
getName s css = ((map intToChar . intToBase 26) <$>) (getIndex s css)
getName s css = (((map intToChar . intToBase 26) <$>) . getIndex s) css
getName s = ((map intToChar . intToBase 26) <$>) . getIndex s
然后你可以按照同样的逻辑去掉s
getName s = ((map intToChar . intToBase 26) <$>) . (getIndex s)
getName s = (((map intToChar . intToBase 26) <$>) .) (getIndex s)
getName s = ((((map intToChar . intToBase 26) <$>) .) . getIndex) s
getName = (((map intToChar . intToBase 26) <$>) .) . getIndex
但这并不比原来的更具可读性。像这样删除参数称为 pointfreeing 函数。它可以基本上对任何功能完成,但应该谨慎地完成,因为你很容易以难以理解的方式结束。
首先让我们eta-reduce讨论一个问题。这就需要把运算符改造成section
getName s css = map intToChar . intToBase 26 <$> getIndex s css
getName s = (map intToChar . intToBase 26 <$>) . getIndex s
又名
getName s = fmap (map intToChar . intToBase 26) . getIndex s
现在,可以将函数 post-composition 视为另一个函子操作†,但需要明确提及:
getName s = fmap (fmap (map intToChar . intToBase 26)) (getIndex s)
这很尴尬。一些库定义了像 <<$>>
这样的运算符,允许将其压缩为
getName s = map intToChar . intToBase 26 <<$>> getIndex s
但这并不能很好地扩展——您需要为每个嵌套深度使用不同的运算符。不过,我们当然可以自己定义它:
infixl 4 <<<$>>>
(<<<$>>>) :: (Functor f, Functor g, Functor h)
=> (a->b) -> f (g (h a)) -> f (g (h b))
(<<<$>>>) = fmap . fmap . fmap
getName = map intToChar . intToBase 26 <<<$>>> getIndex
或者您可以合并,即将 compose 不同的仿函数合并为一个:
getName s = getCompose $ map intToChar . intToBase 26 <$> Compose (getIndex s)
...其中 kind-of 也扩展到包括另一个参数
getName = getCompose . getCompose $ map intToChar . intToBase 26 <$> Compose (Compose getIndex)
但这显然无法战胜之前的任何形式。同样的事情也可以用 ReaderT
monad transformer 来表达,但那样变得同样不可读。对于涉及部分应用组合运算符的各种 point-free 恶作剧也是如此。
tl;dr 这不值得,只要保持原样即可。
†我 一般来说 不是 (c->)
仿函数的粉丝。 Haskell 有足够多的仿函数,但它对普通的旧函数有很好的特殊语法,所以为什么不直接使用它并弄清楚什么仿函数只是一个函数组合。
我有以下问题。我有一个功能很好用。
getName :: Style -> Css -> Maybe String
getName s css = map intToChar . intToBase 26 <$> getIndex s css
我想知道为什么我不能这样定义它:
getName :: Style -> Css -> Maybe String
getName = map intToChar . intToBase 26 <$> getIndex
我认为柯里化可以让我return作为函数接受两个参数而不是一个有两个参数的函数
如果对您的理解有帮助:
intToChar :: Int -> Char
intToBase :: Int -> Int -> [Int]
getIndex :: Style > Css -> Maybe Int
啊,不要忘记错误:
error:
• Couldn't match type ‘[a] -> Maybe Int’ with ‘Int’
Expected type: a -> Int
Actual type: a -> [a] -> Maybe Int
• Probable cause: ‘getIndex’ is applied to too few arguments
In the second argument of ‘(<$>)’, namely ‘getIndex’
In the expression: map intToChar . intToBase 26 <$> getIndex
In an equation for ‘getName’:
getName = map intToChar . intToBase 26 <$> getIndex
• Relevant bindings include
getName :: a -> [Char] (bound at src/Css/Css.hs:17:1)
|
17 | getName = map intToChar . intToBase 26 <$> getIndex
| ^^^^^^^^
问题是 s
和 css
不能通过 eta conversion 消除,因为它们单独用作 getIndex
的参数,而不是 map intToChar . intToBase 26 <$> getIndex
.
要走point-free,就得用函数组合。首先,使用 fmap
而不是 <$>
重写您的定义(并将 fmap
的不相关参数隐藏在局部变量后面)。
getName s css = fmap f (getIndex s css)
where f = map intToChar . intToBase 26
现在更容易看出需要用函数组合先消去css
:
-- f1 (f2 x) == f1 . f2
-- f1 = fmap f
-- f2 = getIndex s
getName s = fmap f . (getIndex s)
where f = map intToChar . intToBase 26
然后 s
:
-- f1 (f2 x) = f1 . f2
-- f1 = (fmap f .)
-- f2 = getIndex
getName = (fmap f . ) . getIndex
where f = map intToChar . intToBase 26
也许需要加上括号。首先,您尝试应用的原则是有效的。也就是说,如果一个变量是函数的最后一个参数并且是函数中 top-level 调用的最后一个参数,则可以有效地“right-cancel”这两个变量,所以如果你有
prependMy x = mappend "my" x
然后你可以安全地“取消”x
并得到等效的(模单态限制,这里不适用但有时会咬你)
prependMy = mappend "my"
现在,这是你的表情。
getName s css = map intToChar . intToBase 26 <$> getIndex s css
为了清楚起见,现在相同的东西加上一些括号
getName s css = (map intToChar . intToBase 26) <$> (getIndex s css)
现在 css
不在 top-level 上了。以前不是,现在更清楚不是了。如果您可以重新排列表达式,使 s
和 css
实际上位于 top-level 调用处,那么您仍然可以取消。在这种情况下,您可以通过执行以下操作来摆脱 css
。
getName s css = (map intToChar . intToBase 26) <$> (getIndex s css)
getName s css = ((map intToChar . intToBase 26) <$>) (getIndex s css)
getName s css = (((map intToChar . intToBase 26) <$>) . getIndex s) css
getName s = ((map intToChar . intToBase 26) <$>) . getIndex s
然后你可以按照同样的逻辑去掉s
getName s = ((map intToChar . intToBase 26) <$>) . (getIndex s)
getName s = (((map intToChar . intToBase 26) <$>) .) (getIndex s)
getName s = ((((map intToChar . intToBase 26) <$>) .) . getIndex) s
getName = (((map intToChar . intToBase 26) <$>) .) . getIndex
但这并不比原来的更具可读性。像这样删除参数称为 pointfreeing 函数。它可以基本上对任何功能完成,但应该谨慎地完成,因为你很容易以难以理解的方式结束。
首先让我们eta-reduce讨论一个问题。这就需要把运算符改造成section
getName s css = map intToChar . intToBase 26 <$> getIndex s css
getName s = (map intToChar . intToBase 26 <$>) . getIndex s
又名
getName s = fmap (map intToChar . intToBase 26) . getIndex s
现在,可以将函数 post-composition 视为另一个函子操作†,但需要明确提及:
getName s = fmap (fmap (map intToChar . intToBase 26)) (getIndex s)
这很尴尬。一些库定义了像 <<$>>
这样的运算符,允许将其压缩为
getName s = map intToChar . intToBase 26 <<$>> getIndex s
但这并不能很好地扩展——您需要为每个嵌套深度使用不同的运算符。不过,我们当然可以自己定义它:
infixl 4 <<<$>>>
(<<<$>>>) :: (Functor f, Functor g, Functor h)
=> (a->b) -> f (g (h a)) -> f (g (h b))
(<<<$>>>) = fmap . fmap . fmap
getName = map intToChar . intToBase 26 <<<$>>> getIndex
或者您可以合并,即将 compose 不同的仿函数合并为一个:
getName s = getCompose $ map intToChar . intToBase 26 <$> Compose (getIndex s)
...其中 kind-of 也扩展到包括另一个参数
getName = getCompose . getCompose $ map intToChar . intToBase 26 <$> Compose (Compose getIndex)
但这显然无法战胜之前的任何形式。同样的事情也可以用 ReaderT
monad transformer 来表达,但那样变得同样不可读。对于涉及部分应用组合运算符的各种 point-free 恶作剧也是如此。
tl;dr 这不值得,只要保持原样即可。
†我 一般来说 不是 (c->)
仿函数的粉丝。 Haskell 有足够多的仿函数,但它对普通的旧函数有很好的特殊语法,所以为什么不直接使用它并弄清楚什么仿函数只是一个函数组合。