在Haskell中实现功能添加

时间:2017-12-26 01:15:23

标签: haskell functional-programming

我在Haskell中有一个难题,

  

f有两个函数,函数a和函数b。函数a接受 n a 输入并返回Num类型,函数b接受 n b < / sub> 输入并返回Num类型。 f返回arity n a + n b 的新功能,将a应用于第一个 n a 参数, n b 到其余参数并返回它们的总和。

在数学中我会写这个:

latex of formula

我在Haskell的第一次天真的尝试是:

f a b = flip ((+) . a) . b

但这仅在a是一元函数时才有效。

在此之后,我想了解这个谜题很长一段时间,甚至没有想出如何做到这一点的想法。这是很长一段时间以来我第一次在哈斯克尔彻底难倒。

我怎么能解决这个难题?这个难题有解决方案吗? (我被朋友给了这个谜题,我不相信他们当时有一个实际的解决方案)

2 个答案:

答案 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 是一个幺半群类别,(,)代表了一种加法。)