今天我要求GHC编译一个8MB的Haskell源文件.GHC考虑了大约6分钟,吞下了近2GB的RAM,然后最终放弃了内存不足的错误.
[顺便说一句,我很高兴GHC很有意义中止而不是整个PC.]
基本上我有一个程序,它读取文本文件,进行一些奇特的解析,构建数据结构然后用于show将其转储到文件中.我不想将整个解析器和源数据包含在我的最终应用程序中,而是将生成的数据作为编译时常量包含在内.通过向输出中添加一些额外的东西show,可以使其成为有效的Haskell模块.但GHC显然不喜欢编译多MB源文件.
(最奇怪的部分是,如果你只read回到数据,它实际上并不需要花费太多时间或内存.奇怪的是,考虑到StringI/O并且read被认为效率非常低......)
我模糊地回忆起其他人在过去使用GHC编译大文件时遇到了麻烦.FWIW,我尝试使用-O0,加速了崩溃但没有阻止它.那么在Haskell程序中包含大型编译时常量的最佳方法是什么?
(在我的例子中,常量只是嵌套Data.Map了一些有趣的标签.)
最初我认为GHC可能只是对阅读由一行长达800万个字符组成的模块感到不满.(!!)与布局规则等有关.或者深深嵌套的表达式可能会扰乱它.但我尝试将每个子表达式设置为顶级标识符,这没有任何帮助.(然而,为每个人添加显式类型签名确实会使编译器更加快乐.)还有什么我可能会尝试使编译器的工作更简单吗?
最后,我能够使我实际上试图存储的数据结构小得多.(比如300KB.)这让GHC更加快乐.(最后的应用程序要快得多.)但是为了将来参考,我有兴趣知道最好的方法是什么.
许多介绍性文本将告诉您,在Haskell类型中,签名"几乎总是"可选的.任何人都可以量化"差不多"的部分吗?
据我所知,您需要显式签名的唯一时间是消除类型类的歧义.(典型的例子是read . show.)还有其他我没有想到的情况,或者是这样吗?
(我知道如果你超越Haskell 2010就有很多例外.例如,GHC永远不会推断排名N类型.但是排名N类型是语言扩展,而不是官方标准[尚未]. )
GHC垃圾收集器是否特别处理"大"对象?或者它对待它们与任何其他对象完全相同?
一些GC引擎将大型对象放在一个单独的区域中,该区域的扫描次数较少,并且可能具有不同的收集算法(例如,压缩而不是复制,或者甚至可能使用空闲列表而不是尝试进行碎片整理).GHC会做这样的事吗?
我看到Hackage 2有一个changelog字段.这是我长期以来想要的功能.但是,多次谷歌搜索未能找到关于如何填充此字段的单一文档.有谁知道怎么做?
我之前问过这个问题,但似乎我的问题太过狭隘.那么让我们看看我是否可以解释我实际上在追求什么.
假设我有一些类型支持几个二元运算符,每个运算符具有不同的优先级和关联性.如何编写Show正确包含子表达式的实例?
我知道我在这里很密集,但每次尝试这样做都会出错.必须有一些机械程序,你可以遵循正确的工作,但我找不到它.有人可以告诉我一个例子吗?
我知道这最终归结为包装所有东西showParen,并使用showsPrec正确的幻数显示子表达式,我可以使它几乎工作,但它在所有情况下都无法正常工作.
编辑:请考虑以下代码
data Expr =
Const Int |
Expr :+: Expr |
Expr :-: Expr |
Expr :*: Expr |
Expr :/: Expr
infixl 6 :+:
infixl 6 :-:
infixl 7 :*:
infixl 7 :/:
instance Show Expr where
showsPrec p e0 =
case e0 of
Const n -> shows n
x :+: y -> showParen (p > 6) $ …Run Code Online (Sandbox Code Playgroud) 我想我可能已经在Haskell-Cafe上问了这个问题,但是如果我现在能找到答案那该死的......所以我在这里再问一遍,所以希望将来我能找到答案!
Haskell 在处理参数多态方面非常出色.但麻烦的是并非一切都是参数化的.作为一个简单的例子,假设我们想要从容器中获取数据的第一个元素.对于参数类型,这是微不足道的:
class HasFirst c where first :: c x -> Maybe x instance HasFirst [] where first [] = Nothing first (x:_) = Just x
现在尝试编写一个实例ByteString.你不能.它的类型没有提到元素类型.你也不能写一个实例Set,因为它需要一个Ord约束 - 但是类头没有提到元素类型,所以你不能约束它.
关联类型提供了一种完整解决这些问题的简洁方法:
class HasFirst c where type Element c :: * first :: c -> Maybe (Element c) instance HasFirst [x] where type Element [x] = x first [] = Nothing first (x:_) = Just x instance HasFirst ByteString where type …
好的,所以这里有一个问题:鉴于Haskell允许您使用任意运算符优先级定义新运算符...如何实际解析Haskell源代码?
在解析源之前,您无法知道设置了哪些运算符优先级.但是,在知道正确的运算符优先级之前,您无法解析源代码.那么......嗯,怎么样?
例如,考虑表达式
x *** y +++ z
Run Code Online (Sandbox Code Playgroud)
在我们完成解析模块之前,我们不知道导入了哪些其他模块,因此操作符(和其他标识符)可能在范围内.我们当然不知道它们的优先级.但是解析器必须返回一些东西 ......但它应该返回
(x *** y) +++ z
Run Code Online (Sandbox Code Playgroud)
或者它应该返回
x *** (y +++ z)
Run Code Online (Sandbox Code Playgroud)
可怜的解析器无法知道.只有在搜索带入(+++)和(***)进入作用域的导入,从磁盘加载该文件并发现运算符优先级时,才能确定这一点.很明显,解析器本身不会完成所有I/O操作; 解析器只是将字符流转换为AST.
显然有人在某处想出了如何做到这一点.但是我无法解决这个问题......有什么提示吗?
Prelude> let myprint = putStrLn . show
Prelude> :t myprint
myprint :: () -> IO ()
Run Code Online (Sandbox Code Playgroud)
好的,这里没什么不寻常的.只是GHCi类型的默认规则,我猜......
Prelude> let myprint = (putStrLn . show) :: Show x => x -> IO ()
Prelude> :t myprint
myprint :: () -> IO ()
Run Code Online (Sandbox Code Playgroud)
这是什么巫术?你是无意识的,忽略了我的类型声明?!O_O
有什么方法可以说服GHCi做我真正的意图吗?
好的,所以我知道Applicative类型类包含什么,以及为什么它有用.但是我不能完全围绕你如何在一个非平凡的例子中使用它.
例如,考虑以下相当简单的Parsec解析器:
integer :: Parser Integer
integer = do
many1 space
ds <- many1 digit
return $ read ds
Run Code Online (Sandbox Code Playgroud)
现在,如果不使用Monad实例,你会怎么写Parser?很多人声称这可以做到并且是个好主意,但我无法弄清楚究竟是怎么回事.
之前我曾询问过将monadic代码翻译为仅使用Parsec的applicative functor实例.不幸的是,我得到了几个回复,回答了我真正问过的问题,但并没有给我太多的了解.那么让我再试一次......
总结我到目前为止的知识,一个应用函子比一个monad更受限制.在"少即是多"的传统中,限制代码可以做什么会增加疯狂代码操作的可能性.无论如何,很多人似乎相信使用applicative而不是monad是一种可行的优越解决方案.
在Applicative类的定义中Control.Applicative,它的黑线鳕的上市有益分离类的方法和实用功能与他们之间的类实例的广阔裹,使其很难迅速在屏幕上看到的一切在一次.但相关的类型签名是
pure :: x -> f x
<*> :: f (x -> y) -> f x -> f y
*> :: f x -> f y -> f y
<* :: f x -> f y -> f x
<$> :: (x -> y) -> f x -> f y
<$ :: x -> f y -> f x
Run Code Online (Sandbox Code Playgroud)
做得很完美,对吧?
好吧,Functor已经给了我们fmap,基本上就是这样<$>.即,由于从功能x到y,我们可以映射一个f …