我在Haskell中有一个难题,
f
有两个函数,函数a
和函数b
。函数a
接受 n a 输入并返回Num
类型,函数b
接受 n b < / sub> 输入并返回Num
类型。f
返回arity n a + n b 的新功能,将a
应用于第一个 n a 参数, n b 到其余参数并返回它们的总和。
在数学中我会写这个:
我在Haskell的第一次天真的尝试是:
f a b = flip ((+) . a) . b
但这仅在a
是一元函数时才有效。
在此之后,我想了解这个谜题很长一段时间,甚至没有想出如何做到这一点的想法。这是很长一段时间以来我第一次在哈斯克尔彻底难倒。
我怎么能解决这个难题?这个难题有解决方案吗? (我被朋友给了这个谜题,我不相信他们当时有一个实际的解决方案)
答案 0 :(得分:7)
这是一种非常简单的方法,它使用在数字类型中单态工作的类型族(例如,专门用于Int
)。我们需要一些扩展程序:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
函数f
将在类型类中定义:
class VarArgs r s where
type F r s
f :: r -> s -> F r s
我们将处理以下案件。如果第一个函数的类型是a :: Int -> r
形式,我们将使用以下实例来吞并一个参数x
并将其提供给a
:
instance VarArgs r s => VarArgs (Int -> r) s where
type F (Int -> r) s = Int -> F r s
f :: (Int -> r) -> s -> Int -> F r s
f a b x = f (a x) b
这具有递归a
类型的效果,直到它为Int
形式。然后,我们将使用类似的实例来递归类型b :: Int -> s
:
instance VarArgs Int s => VarArgs Int (Int -> s) where
type F Int (Int -> s) = Int -> F Int s
f :: Int -> (Int -> s) -> Int -> F Int s
f a b x = f a (b x)
最终,两个函数都将简化为a, b :: Int
类型的0-ary函数,我们可以使用终端实例:
instance VarArgs Int Int where
type F Int Int = Int
f :: Int -> Int -> Int
f a b = a + b
这是一个证明它有效的小测试:
times2 :: Int -> Int -> Int
times2 x y = x * y
times3 :: Int -> Int -> Int -> Int
times3 x y z = x * y * z
foo :: [Int]
foo = [ f times2 times2 1 2 3 4
, f times2 times3 1 2 3 4 5
, f times3 times2 1 2 3 4 5
, f times3 times3 1 2 3 4 5 6]
并将其加载到GHCi中:
> foo
[14,62,26,126]
>
在任何Num
类型中将其概括为多态并不是直截了当的。用受约束的Int
类型替换Num n
类型会导致有关冲突的族实例声明的错误。
答案 1 :(得分:2)
这很容易也很简单 - 在我看来比@KABuhr的类型族方法简单得多 - 如果你调整你的 n -ary函数的表示,而不是使用一元 n - 维向量的函数。
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
import Prelude hiding (splitAt)
import Data.Bifunctor
通常的嫌疑人:(类型级别)自然数,他们的(价值级)单例,类型级别加法和向量。
data Nat = Z | S Nat
data Natty n where
Zy :: Natty Z
Sy :: Natty n -> Natty (S n)
type family n + m where
Z + m = m
S n + m = S (n + m)
data Vec n a where
Nil :: Vec Z a
(:>) :: a -> Vec n a -> Vec (S n) a
splitAt
需要一个运行时Natty
- 它需要在运行时知道在哪里拆分向量 - 以及一个至少与Natty
一样长的向量。
splitAt :: Natty n -> Vec (n + m) a -> (Vec n a, Vec m a)
splitAt Zy xs = (Nil, xs)
splitAt (Sy n) (x :> xs) =
let (ys, zs) = splitAt n xs
in (x :> ys, zs)
然后,我f
,我正在呼叫splitApply
,是splitAt
的简单应用。
splitApply :: Natty n -> (Vec n a -> b) -> (Vec m a -> c) -> Vec (n + m) a -> (b, c)
splitApply at f g xs = bimap f g $ splitAt at xs
(我还没有打算显示&#34;添加结果&#34;部分,因为它非常简单以至于我自己写的很无聊。你可以说,因为 Hask 是一个幺半群类别,(,)
代表了一种加法。)