3

可能重复:
为什么在haskell中不允许这样的函数定义?

我做了一个名为 .haskell 的函数funlist。它的作用是接受一个起始值和一个函数列表,并将列表中的所有函数应用于起始值。

funlist thing [function] = function thing
funlist thing (function:functions) = funlist (function thing) functions
funlist _ _ = error "need a list of functions"

这个函数的问题是它的类型是funlist :: t -> [t -> t] -> t. 该类型意味着虽然 ghc 将允许不将起始值转换为完全不同类型的函数列表(例如[sin,cos,tan]将被允许),但将起始值转换为不同类型(例如show)的函数将产生错误因为该函数与类型签名不匹配。

这不是该功能应该如何工作的方式。它应该能够获取更改起始值类型(例如[sin,show])的函数列表。这个函数基本上转换funlist 5 [sin,cos,tan,isInfinite,show]show $ isInfinite $ tan $ cos $ sin $ 5,虽然后者有效,但前者无效。

有什么办法可以让这个功能正常工作吗?

编辑:我知道.and >>>,我只是想知道是否有办法使这项工作。

4

7 回答 7

7

您可以使用 GADT 编写您想要的内容:

{-# LANGUAGE GADTs #-}
module Funlist where

data F x y where
  Id :: F a a
  Ap :: (a->b) -> F b c -> F a c

-- A very round about way to write f x = x + x

f1 :: Int -> Char
f1 = toEnum

f2 :: Char -> String
f2 x = x:x:[]

f3 :: String -> [Int]
f3 = map fromEnum

f4 :: [Int] -> Integer
f4 = foldr (+) 0 . map toInteger

f_list :: F Int Integer
f_list = Ap f1 (Ap f2 (Ap f3 (Ap f4 Id)))

ap :: F a b -> a -> b
ap Id x = x
ap (Ap f gs) x = ap gs (f x)

现在ap f_list 65130

于 2012-07-18T19:37:51.110 回答
6

这不适用于 Haskell 中的普通函数/普通列表,因为它需要动态类型语言,而不是像 Haskell 这样的静态类型语言。根据funlist运行时函数列表的内容,函数不能有不同的类型;它的类型必须在编译时知道。此外,编译器必须能够检查函数链是否有效,这样您就不能使用[tan, show, sin]例如列表。

这个问题有两种解决方案。

您可以使用异构列表。这些列表可以存储每个元素都是不同类型的列表。然后,您可以检查每个元素必须是函数并且一个元素返回类型必须是下一个函数的参数类型的约束。这很快就会变得非常困难。

您还可以使用Data.Dynamic让您的函数采用和返回动态类型。在这种情况下,您必须执行一些动态类型转换。

于 2012-07-18T19:25:30.623 回答
6

如果您对这个函数列表所做的只是将它们应用于管道中的单个值,那么不要编写和调用您的funlist函数,而是执行以下操作:

show . isInfinite . tan . cos . sin $ 5

或者,如果您不想在代码中反转列表,请执行以下操作:

import Control.Arrow (>>>)

(sin >>> cos >>> tan >>> isInfinite >>> show) 5
于 2012-07-18T19:29:48.237 回答
4

通常,Haskell 中的函数具有看起来像 的类型,对于和a -> b的某些选择。在你的情况下,你有一个函数列表,你想计算这个:ab[f0, ..., fn]

funlist [f0, ..., fn] x == f0 (funlist [f1, ..., fn] x)
                        == f0 (f1 (funlist [f2, ..., fn] x))
                        ...
                        == f0 (f1 (... (fn x)))

t -> t您遇到的问题是以下两件事的结果:

  1. 此计算要求 的参数类型为f0的返回类型f1, 的参数类型为f1的返回类型f2,依此类推:f0 :: y -> z, f1 :: x -> y, ..., fn :: a -> b
  2. 但是你将所有这些函数放在一个列表中,并且 Haskell 中列表的所有元素都必须具有相同的类型。

这两个加在一起,意味着 中使用的函数列表funlist必须具有 type [t -> t],因为这是同时满足两个条件的唯一方法。

除此之外,dave4420 的答案是最好的简单答案,IMO:使用函数组合。如果你不能使用它,因为要完成的计算只在运行时知道,那么你希望有一些比列表更复杂的数据结构来表示可能的计算。Chris Kuklewicz 对此提出了一个非常通用的解决方案,但我通常会针对手头的特定问题区域做一些定制的事情。

也很高兴知道你funlist可以这样写:

funlist :: a -> [a -> a] -> a
funlist x fs = foldr (.) id fs x
于 2012-07-18T23:44:51.083 回答
2

简短的回答:不,没有办法用列表做你想做的事(至少以一种明智的方式)。

原因是 Haskell 中的列表总是同质的,即列表的每个元素必须具有相同的类型。您要添加到列表中的函数具有以下类型:

sin :: Floating a => a -> a
isInfinite :: Floating b => b -> Bool
show :: Show c => c -> String

所以你不能把函数放在同一个列表中。您的两个主要选择是:

  1. 使用列表以外的结构(例如 HList 或自定义 GADT)
  2. 使用动态类型

由于其他答案已经给出了 GADT 示例,以下是使用动态类型实现函数的方法:

import Data.Dynamic

funlist :: Dynamic -> [Dynamic] -> Dynamic
funlist thing (function:functions) = funlist (dynApp function thing) functions
funlist thing [] = thing

但是,使用动态类型会导致一些样板,因为您必须在静态和动态类型之间进行转换。因此,要调用该函数,您需要编写

funlist (toDyn 5) [toDyn sin, toDyn cos, toDyn tan, toDyn isInfinite, toDyn show]

不幸的是,即使这样还不够。下一个问题是动态值必须具有同态类型,因此例如,show :: Show a => a -> String您需要手动指定具体类型而不是函数show :: Bool -> String,因此上述变为:

funlist (toDyn (5::Double)) [toDyn sin, toDyn cos, toDyn tan, toDyn isInfinite,
    toDyn (show :: Bool -> String)]

而且,函数的结果是另一个动态值,所以如果我们想在常规函数中使用它,我们需要将它转换回静态值。

fromDyn (funlist (toDyn (5::Double)) [toDyn sin, toDyn cos, toDyn tan,
    toDyn isInfinite, toDyn (show :: Bool -> String)]) ""
于 2012-07-19T06:33:52.487 回答
1

你想要的东西在 Haskell 中有效,但它不是一个列表。它是一个函数组合,实际上可以包装在 GADT 中:

import Control.Arrow
import Control.Category
import Prelude hiding ((.), id)

data Chain :: * -> * -> * where
    Chain :: (a -> c) -> Chain c b -> Chain a b
    Id    :: Chain a a

apply :: Chain a b -> a -> b
apply (Chain f k) x = apply k (f x)
apply Id x          = x

现在您可以在一定程度上检查函数链的结构。您可以找到的信息不多,但Chain如果您需要更多信息,可以向构造函数添加更多元信息。

该类型还形成了一个有趣的类别,它保留了附加信息:

instance Category Chain where
    id = Id

    Id . c           = c
    c  . Id          = c
    c2 . Chain f1 k1 = Chain f1 (c2 . k1)

instance Arrow Chain where
    arr f = Chain f Id

    first (Chain f c) = Chain (first f) (first c)
    first Id = Id
于 2012-07-18T23:39:50.143 回答
0

那里有一些使用 GADT 的答案,这是做这些事情的好方法。我想在这里补充的是,这些答案中使用的结构已经以更一般的方式存在:它被称为thrist(“类型线程列表”):

Prelude Data.Thrist> let fs = Cons (show :: Char -> String) (Cons length Nil)
Prelude Data.Thrist> let f = foldl1Thrist (flip (.))  fs
Prelude Data.Thrist> :t fs
fs :: Thrist (->) Char Int
Prelude Data.Thrist> :t f
f :: Char -> Int
Prelude Data.Thrist> f 'a'
3

当然,你也可以foldl1Thrist (>>>) fs改用。请注意,thrists 构成一个类别、一个箭头和一个幺半群(带有appendThrist)。

于 2012-07-19T09:59:18.117 回答