Haskell的类型安全性仅对于依赖类型的语言是首屈一指的.但是Text.Printf有一些深刻的魔力似乎相当类型.
> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
Run Code Online (Sandbox Code Playgroud)
这背后的深层魔力是什么?该Text.Printf.printf
函数如何采用像这样的可变参数?
用于允许Haskell中的可变参数的一般技术是什么,它是如何工作的?
(旁注:使用这种技术时,某种类型的安全性显然会丢失.)
> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
Run Code Online (Sandbox Code Playgroud) 我需要一个函数,它接受任意数量的参数(所有相同的类型),对它们做一些事情然后给出一个结果.在我的具体情况下,参数列表是不切实际的.
当我查看haskell库时,我看到函数printf
(来自模块Text.Printf
)使用了类似的技巧.不幸的是,通过查看来源我无法理解这种魔力.
有人可以解释如何实现这一点,或至少一些网页/纸/任何我可以找到一个良好的描述吗?
动机:
我需要它的原因非常简单.对于学校(计算机科学课),我们需要编写一个能够"记录"数学表达式,将其表示为字符串的模块(通过为自己的数据类型编写Num/Real/etc的实例),并执行对它的各种操作.
此数据类型包含变量的特殊构造函数,可以由值或指定函数的任何值替换.其中一个目标是编写一个函数,它使用一些变量(类型对(Char,Rational)
)来表达这种表达式并计算表达式的结果.我们应该看看如何最好地表达函数的目标.(我的想法:该函数返回另一个函数,它接受与函数中定义的变量一样多的参数 - 似乎是不可能的).
可能重复:
为什么haskell中不允许这样的函数定义?
我是Haskell世界的新手,从Lisp迁移过来.我正在努力适应Haskell根本不同的世界观,而我发现的许多令人兴奋的事情之一就是类型系统.作为一个Lisper,我想我会尝试在Haskell中实现一个在Lisp世界中非常重要的函数:apply
.对于那些不知道的人,apply接受一个函数和一个参数列表,并在这些参数上调用该函数.在Scheme中,(apply + '(1 2 3))
与调用相同(+ 1 2 3)
,并返回6.
我的Haskell代码看起来像这样:
apply x [] = x
apply f (x:xs) = apply (f x) xs
Run Code Online (Sandbox Code Playgroud)
但哈斯克尔抱怨道:
ERROR line 2 - Type error in function binding
*** Term : apply
*** Type : (b -> a) -> [b] -> a
*** Does not match : a -> [b] -> a
*** Because : unification would give infinite type
Run Code Online (Sandbox Code Playgroud)
而且我认为我理解为什么.Apply的类型需要根据列表的长度而有所不同.给出一个例如3个项目的列表,apply的类型需要是:(a -> a -> a -> b) …
在阅读了关于在Haskell中编写多变量函数的这篇文章后,我试着写一些自己的函数.
起初我以为我会尝试概括它 - 所以我可以通过折叠参数给出一个返回可变参数函数的函数.
{-# OPTIONS -fglasgow-exts #-}
module Collapse where
class Collapse a r | r -> a where
collapse :: (a -> a -> a) -> a -> r
instance Collapse a a where
collapse _ = id
instance (Collapse a r) => Collapse a (a -> r) where
collapse f a a' = collapse f (f a a')
Run Code Online (Sandbox Code Playgroud)
但是,编译器不喜欢这样:
Collapse.hs:5:9:
Functional dependencies conflict between instance declarations:
instance Collapse a a -- Defined at …
Run Code Online (Sandbox Code Playgroud) 我目前定义任意arity函数的方法如下,A是累加器,E是输入参数类型,R是结果类型.
combine :: A -> E -> A
class X r where
foo :: A -> E -> r
instance X R where
foo :: A -> E -> R
instance X r => X ( E -> r ) where
foo :: A -> E -> E -> r
foo ( a :: A ) ( x :: E ) =
foo ( a `combine` e :: A )
doFoo = foo emptyA
Run Code Online (Sandbox Code Playgroud)
但是foo的最小arity是1.foo的最小值仍然是A - > E - > …
是否有可能有一个函数接受外部函数调用,其中一些外部函数的参数是CString并返回一个接受String的函数?
这是我正在寻找的一个例子:
foreign_func_1 :: (CDouble -> CString -> IO())
foreign_func_2 :: (CDouble -> CDouble -> CString -> IO ())
externalFunc1 :: (Double -> String -> IO())
externalFunc1 = myFunc foreign_func_1
externalFunc2 :: (Double -> Double -> String -> IO())
externalFunc2 = myFunc foreign_func_2
Run Code Online (Sandbox Code Playgroud)
我想出了如何使用C数字类型执行此操作.但是,我无法找到一种可以允许字符串转换的方法.
这个问题似乎适合IO函数,因为转换为CStrings的所有内容(如newCString或withCString)都是IO.
以下是处理转换双精度的代码.
class CConvertable interiorArgs exteriorArgs where
convertArgs :: (Ptr OtherIrrelevantType -> interiorArgs) -> exteriorArgs
instance CConvertable (IO ()) (Ptr OtherIrrelevantType -> IO ()) where
convertArgs = doSomeOtherThingsThatArentCausingProblems
instance (Real b, Fractional a, CConvertable intArgs extArgs) …
Run Code Online (Sandbox Code Playgroud) 今天我玩了使用类型类来归纳地构造任何arity谓词的函数,将任何类型的任意组合作为输入,返回相同类型的其他谓词但应用了一些基本操作.例如
conjunction (>2) even
Run Code Online (Sandbox Code Playgroud)
会返回一个谓词,对于大于2的偶数,它的计算结果为真
conjunction (>=) (<=)
Run Code Online (Sandbox Code Playgroud)
会返回=
一切都很好,让这部分工作,但它提出了一个问题,如果我想将两个谓词的连接定义为一个谓词,为每个连接谓词的每个输入获取一个输入,该怎么办?例如:
:t conjunction (>) not
Run Code Online (Sandbox Code Playgroud)
将返回Ord a => a - > a - > Bool - > Bool.可以这样做吗?如果是这样,怎么样?
这个答案演示了一个多变量函数,它总结了它的参数:
class SumRes r where
sumOf :: Integer -> r
instance SumRes Integer where
sumOf = id
instance (Integral a, SumRes r) => SumRes (a -> r) where
sumOf x = sumOf . (x +) . toInteger
Run Code Online (Sandbox Code Playgroud)
我为以下所有成员创建了此函数的通用版本Num
:
class (Num n) => MySumType n r where
mySum :: n -> r
instance (Num n) => MySumType n n where
mySum x = x
instance (Num n, MySumType n r) => MySumType n (n->r) where
mySum x = …
Run Code Online (Sandbox Code Playgroud) 所以,我正在尝试实现这里描述的多变量ZipWithN .不幸的是,Paczesiowa的代码似乎是用ghc和HList的过时版本编译的,所以在试图理解它如何工作的过程中,我也将它移植到这两个版本的最新版本(ghc- 7.8.3和此时的HList-0.3.4.1).这很有趣.
无论如何,在中间函数的定义中,我遇到了谷歌没有帮助我修复一次的错误curryN'
.在概念上,curryN'
很简单:它采用类型级自然数N
(或严格来说,该类型的值),以及f
第一个参数是长度为HList的函数N
,并返回一个N
带有HList的-ary函数超出其第一个N
参数,并将返回f
应用于该HList.它是curry
,但是多变量.
它使用三个辅助函数/类:
第一个是ResultType
/ resultType
,正如我在这里定义的那样. resultType
将单个函数作为参数,并在将该函数应用于所需的参数之后返回该函数的类型.(严格地说,它再次返回该类型的未定义值).
例如:
ghci> :t resultType (++)
resultType (++) :: [a]
ghci> :t resultType negate
resultType negate :: (ResultType a result, Num a) => result
Run Code Online (Sandbox Code Playgroud)
(后一种情况,因为如果a
恰好是类型的函数x -> y
,则resultType必须返回y
.因此它不适用于多态函数.)
后两个是和Eat
/ eat
和MComp
/ mcomp
,一起定义(连同curryN'
)在一个文件中(连同破碎curryN'
)像这样 …
如果我想编写一个方法,它采用可变数量的"TDerived",其中TDerived是类"Base"的任何子类,有没有办法做到这一点?
以下代码仅适用于单个特定的指定子类:
void doStuff<TDerived>(params TDerived[] args) where TDerived : Base
{
//stuff
}
Run Code Online (Sandbox Code Playgroud)
即如果我有
class Super { }
class Sub0 : Super { }
class Sub1 : Super { }
Run Code Online (Sandbox Code Playgroud)
那我就做不到了
Sub0 s0 = new Sub0();
Sub1 s1 = new Sub1();
doStuff(s0, s1);
Run Code Online (Sandbox Code Playgroud)
因为我得到"最好的重载匹配......有一些无效的论点".
无论编译器如何处理类型约束和可变参数函数,这似乎(据我所知)完全类型安全.我知道我可以施放,但如果这是类型安全的,为什么不允许它?
编辑:
也许是一个更有说服力的例子:
void doStuff<TDerived>(params SomeReadOnlyCollection<TDerived>[] args) where TDerived : Base
{
foreach(var list in args)
{
foreach(TDerived thing in list)
{
//stuff
}
}
}
Run Code Online (Sandbox Code Playgroud)