【问题标题】:What type signature do I need to allow a list of functions to be converted to haskell code? [duplicate]我需要什么类型的签名才能将函数列表转换为 haskell 代码? [复制]
【发布时间】:2012-07-17 22:07:14
【问题描述】:

可能重复:
Why is such a function definition not allowed in haskell?

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

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] 将被允许),但将起始值转换为不同类型的函数(例如 @987654326 @) 将产生错误,因为该函数与类型签名不匹配。

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

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

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

【问题讨论】:

  • 如果你用funlist :: a -> [a->b] -> b?帮助类型系统是否有效
  • 为什么不构建一个函数并像在列表中使用 (:) 一样使用 (.) 而不是构建函数列表?如果您需要其他信息,例如已经堆叠了多少函数,我建议为此使用 state monad。
  • …如果你用 'a -> [a->b] -> b' 帮助类型系统,你不能链接函数。
  • @sdcvvc :它们非常相似,但询问的是参数列表而不是函数列表。在抽象层面上,它们相似,但答案不同。
  • @GeorgeStocker 是这里真正需要的版主干预,如果是这样,为什么不由至少在其 507 个标签中显示“haskell”标签的人进行干预?

标签: haskell


【解决方案1】:

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

原因是 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]

不幸的是,即使这样还不够。下一个问题是动态值必须具有 homomorphic 类型,因此例如,您需要手动指定函数 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)]) ""

【讨论】:

    【解决方案2】:

    那里有一些使用 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)。

    【讨论】:

      【解决方案3】:

      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 中使用的函数列表必须具有[t -> t] 类型,因为这是同时满足两个条件的唯一方法。

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

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

      funlist :: a -> [a -> a] -> a
      funlist x fs = foldr (.) id fs x
      

      【讨论】:

        【解决方案4】:

        你想要的东西在 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
        

        【讨论】:

          【解决方案5】:

          您可以使用 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

          【讨论】:

          • 请注意,ap (Ap f gs) x = ap gs $! (f x) 会在中间步骤为您提供严格的评估。这可能有助于长函数链。
          • 这是这里提到的自反传递闭包构造(Kleene 星)的 (->) 实例stackoverflow.com/questions/10777283/…
          • @Lambdageek :几乎是 (>>>) 的另一种语法。但是可以做嵌套 F 的“长度”。并且可以制作更精细的 GADT(例如添加 Typeable 类约束)。
          • @pigworker :是的,Star GADT 是我的F 的一个更抽象的例子。
          【解决方案6】:

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

          show . isInfinite . tan . cos . sin $ 5
          

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

          import Control.Arrow (>>>)
          
          (sin >>> cos >>> tan >>> isInfinite >>> show) 5
          

          【讨论】:

          • 谢谢,但我已经知道这些功能了。我只是想让我制作的功能正常工作。
          【解决方案7】:

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

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

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

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

          【讨论】:

            猜你喜欢
            • 2015-01-19
            • 1970-01-01
            • 2021-03-12
            • 2020-01-27
            • 1970-01-01
            • 2018-08-22
            • 2023-03-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多