首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

为什么Haskell不能优化这个重复的函数调用?

Haskell 不能优化重复的函数调用是因为 Haskell 是一种纯函数式编程语言,它遵循了引用透明性的原则。在 Haskell 中,函数的返回值只依赖于其输入参数,而不受外部环境的影响。这种特性使得 Haskell 具有很高的表达能力和代码的可读性,但也限制了编译器进行一些常见的优化。

当一个函数被多次调用时,按照引用透明性的原则,每次调用都必须重新计算函数的结果,而无法复用之前的计算结果。这导致了重复的函数调用,从而降低了程序的性能。

然而,虽然 Haskell 不能自动优化重复的函数调用,但开发者可以手动使用技巧来避免重复计算,提高程序的性能。其中一种常见的技巧是使用惰性求值(Lazy Evaluation)。通过使用惰性求值,可以延迟函数的计算,只在需要的时候才进行实际的计算,并将结果缓存起来以供后续使用。这样可以避免重复计算相同的函数调用。

另外,Haskell 还提供了一些优化工具和技术,如严格求值(Strict Evaluation)和严格数据类型(Strict Data Types),可以在某些情况下改善性能。严格求值可以强制函数在调用时立即计算,而不是延迟计算,从而避免了不必要的惰性求值开销。严格数据类型可以确保数据在创建时立即求值,而不是延迟求值。

总之,尽管 Haskell 不能自动优化重复的函数调用,但开发者可以通过手动使用惰性求值、严格求值和严格数据类型等技巧来提高程序的性能。在实际开发中,根据具体的场景和需求,选择合适的优化策略是非常重要的。

页面内容是否对你有帮助?
有帮助
没帮助

相关·内容

为什么start方法不能重复调用?而run方法却可以?

为什么start不能重复调用?...要找到这个问题答案,就要查看 start 方法实现源码,它源码如下: 从 start 源码实现第一行,我们就可以得到问题答案,因为 start 方法在执行时,会先判断当前线程状态是不是等于...0,也就是是否为新建状态 NEW,如果不等于新建状态,那么就会抛出“IllegalThreadStateException”非法线程状态异常,这就是线程 start 方法不能重复调用原因。...执行速度不同:调用 run 方法会立即执行任务,调用 start 方法是将线程状态改为就绪状态,不会立即执行。 调用次数不同:run 方法可以被重复调用,而 start 方法只能被调用一次。...start 方法之所以不能重复调用原因是,线程状态是不可逆,Thread 在 start 实现源码中做了判断,如果线程不是新建状态 NEW,则会抛出非法线程状态异常 IllegalThreadStateException

70910
  • Flink SQL代码生成与UDF重复调用优化

    通过代码生成,可以将原本需要解释执行算子逻辑转为编译执行(二进制代码),充分利用JIT编译优势,克服传统Volcano模型虚函数调用过多、对寄存器不友好缺点,在CPU-bound场景下可以获得大幅性能提升...本文就来做个quick tour,并提出一个小而有用优化。...它作用就是维护代码生成过程中各种能够重复使用逻辑,包括且不限于: 对象引用 构造代码、初始化代码 常量、成员变量、局部变量、时间变量 函数体(即Flink Function)及其配套(open()...由于RexNode很多变(字面量、变量、函数调用等等),它巧妙地利用了RexVisitor通过访问者模式来将不同类型RexNode翻译成对应代码。...,自定义函数SplitQueryParamsAsMap就会被调用N次,这显然是不符合常理——对于一个确定输入query_string,该UDF输出就是确定,没有必要每次都调用

    1.6K10

    C++对象优化--减少不必要函数调用

    ,背后居然11次函数调用,效率太低了!!!...,又减少了两次函数调用开销!!!...// t2 = getObject(t1); // 赋值方式接受 return 0; } 执行结果: 相对于初始代码,同样是获取一个对象功能,优化到现在只有两次构造和两次析构调用,程序减少了相当大一部分函数调用开销...一次调用getObejct()函数可以减少7次函数调用开销,那么100万次调用,就能减少700万次开销。量变产生质变!!!...对象优化三条规则 1.当函数形式参数需要传递对象时,不要用值接受,用引用接受。减少一次临时对象构造和析构。 2.当函数返回值为对象时,不要再函数题先定义好零时对象,然后再返回值。

    48230

    【手记】走近科学之为什么明明实现了IEnumerable类型却不能调用LINQ扩展方法

    比如Json.NETJObject明明实现了IEnumerable,具体来说是IEnumerable>,按说JObject类型对象是可以直接调用...Select、Where等linq扩展方法,但偏偏就是不行,代码如下: using System.Linq; ... var jobj = new JObject(); var xxx = jobj.Select...(x=>x); //报错:JObject未包含Select定义,也不存在第1个参数为JObjectSelect扩展方法... foreach(var x in jobj) { } //可以 var xxx...= ((IEnumerable>)jobj).Select(x=>x); //也可以 究竟是人性扭曲还是道德沦丧?...,等于JObject直接和间接实现了两个不同IEnumerable,当.Select时候编译器不能确定调用哪个类型Select,所以就这样了。

    80010

    创建子类对象时,父类构造函数调用被子类重写方法为什么调用是子类方法?

    public static void main(String[] args) { A a = new A(); B b = new B(); } } 问题:为什么创建...A对象时候父类会调用子类方法?...但是:创建B对象父类会调用父类方法? 答案: 当子类被加载到内存方法区后,会继续加载父类到内存中。...当子类对象创建时,会先行调用父类构造方法(构造方法也是方法),虚拟机会在子类方法区寻找该方法并运行。 但是:由于java语言是静态多分派,动态单分派。...其结果是当编译时候,父类构造方法调用方法参数已经强制转换为符合父类方法参数了。 上边代码在编译前已经转换为下面这个样子了。

    6.2K10

    从 Java 和 JavaScript 来学习 Haskell 和 Groovy(DSL)

    前文已经介绍过了高阶函数使用,但是在 Haskell 中,所有的函数都可以理解为,每次调用最多都只接受一个参数,如果有多个参数怎么办?...把它化简为多次调用嵌套,而非最后一次调用,都可视为高阶函数(返回函数函数)。...如果递归函数递归调用自己只发生在最后一步,并且程序可以把这一步入栈操作给优化掉,也就是最终可以使用常量栈空间,那么就可以说这个程序/语言是支持尾递归。 它有什么好处?...因为对于常规语言,如果面临递归工作栈过深问题,可以优化为循环解决问题;但是在 Haskell 中,是没有循环语法,这就意味着必须用尾递归来解决这个本来得用循环才能解决问题。...听起来简单,但是只有 Haskell 是真正支持惰性求值,其他语言最多是在很局限范围内,基于优化语言运行性能目的,运行时部分采用惰性求值而已。

    48310

    《我第一个面向需求Haskell程序》续

    前言 上一篇《我第一个面向需求Haskell程序》文章中Haskell程序还存在一个问题: 程序只打印出了文件中有没有重复元素但是并没有告知是哪一个元素重复了,重复了几次也没有打印出来。...所以我继续优化下上篇文章中Haskell程序,现在这段程序变成了下面这样 代码 module Main where import Data.List.Split import Data.List import...函数按照换行符将文件内容切分为[String],现在我们有了: ["abc", "abc", "def", "ghi", "def"] 然后使用group函数聚合下这个List,得到: [["abc"..., "abc", "abc"], ["def", "def"], ["ghi"]] 再通过fmap (\(x:xs) -> (x, 1 + length xs))即map一个lambda表达式到这个List...上,将这个List中每个元素转为元组,得到: [("abc", 3), ("def", 2), ("ghi", 1)] 至此我们实际做了一个WordCount程序… 接下来调用printRepeat函数打印出来结果就

    9810

    第一个面向需求Haskell程序

    由于导出口令有数百万之多,肯定是不能用眼去看了,原本是打算用excel来检查,但是我一想:ei(二声)~,最近不是正好在搞Haskell吗?正好拿来练练手,用Haskell写个检测程序。...Why is Haskell 因为这个程序写出来是要交给测试同学使用,如果用java或者php这种解释型语言来写,还需要测试同学先去安装个java/php解释器才行,显然是有点扯,所以用编译型语言写完后直接...首先,使用cabal创建一个项目 $ mkdir repeat && cd repeat $ cabal init 导出口令文件是以\r\n换行haskelllines函数无法切分,所以需要通过..." else putStrLn "没有重复元素" check x = putStrLn "请输入文件名" -- 通过split库splitOn函数以\r\n为切割符将文件内容切分为...后续优化请看 《我第一个面向需求Haskell程序》续

    9010

    美团一面:能不能通俗解释下为什么要有意向锁这个东西?

    导 读 面试真题,用通俗例子解释清楚 MySQL 为什么有了表锁和行锁之后,还要引入意向锁 众所周知,InnoDB 中既有读锁也有写锁,也称为共享锁和排他锁,这两种锁既可以加在整张表上,也可以加在行上...看下面这个例子: 事务 A 加了行级读锁,锁住了表中一行,让这一行只能读,不能写。 之后,事务 B 尝试申请整个表写锁。...那数据库要怎么判断这个冲突呢?...(行级写锁)时,InnoDB 存储引擎会自动地先获取该表意向写锁(表级锁) 注意这里自动:申请意向锁动作是数据库完成,就是说,事务 A 申请一行行锁时候,数据库会自动先开始申请表意向锁,不需要我们程序员使用代码来申请...也就是说原先步骤 2 遍历表中每一行操作,简化成了判断下整张表上有无表级意向锁就行了,效率大幅提升。 这就是为什么要有意向锁了。 End.

    72220

    nextline函数_在JAVA中Scanner中next()和nextLine()为什么不能一起使用?

    、tab 键、enter 键都不能当作结束符。...“abc” 情况分析: 下一次我们输入是 abc\r,此时这个就是缓冲区全部内容 所以下一次 nextLine 调用时候,就返回 abc,再把 \r 去掉 输入 2: 2 abc bcf efg...这个扫描器在扫描过程中判断停止依据就是“结束符”,空格,回车,tab 都算做是结束符 而坑点在于 next 系列,也就是下面这些函数:next nextInt nextDouble nextFloat...这些函数与 nextLine 连用都会有坑 坑点就是 next 系列函数返回了数据后,会把回车符留在缓冲区,因此我们下一次使用 nextLine 时候会碰到读取空字符串情况 解决方案:输入都用...nextLine ,做格式转换 输入 next 系列函数调用后,中间调用一次 nextLine 调用去掉了回车符后,再调用一次 nextLine 调用真正输入我们数据 都使用 nextLine: class

    2.7K10

    精读《深度学习 - 函数式之美》

    and Haskell 就很好诠释了这个道理。...所以为什么函数式编程语言可以胜任深度学习计算要求呢? 深度学习计算模型本质上是数学模型,而数学模型本质上和函数式编程思路是一致:数据不可变且函数间可以任意组合。...Clojure 凭借 partition 对计算进行分区,采取分而治之并对分区计算结果进行合并思路优化了并发性能。...3 总结 本文介绍了为什么深度学习更适合使用函数式语言,以及介绍了 Clojure 与 Haskell 语言共性:安全性、高性能,以及各自独有的特性,证明了为何这两种语言更适合用在深度学习中。...在前端领域说到函数式或函数之美,大部分时候想到是 Class Component 与 Function Component 关系,这个理解是较为片面的。

    41610

    听君一席话,如听一席话,解释解释“惰性求值”~

    longCalculation1(a,b)、longCalculation2(b,c)、longCalculation3(a,c),longCalculation1/2/3 顾名思义,是一些包含很长计算过程函数...然后在真正需要计算g x时候才会调用这个thunk; 事实上这个thunk里面还包含一个boolean表示该thunk是否已经被计算过(若已经被计算过,则还包含一个返回值),用来防止重复计算;...第一节示例 JavaScript 代码虽然是有惰性求值思想体现,但是其本身并不是惰性求值; 惰性求值是编程语言特性设计,很多纯粹函数式编程语言都支持这种设计; 比如在 Haskell 中实现上述示例...有点像 Promise 意思,你不告诉我 resolve/reject,我就 pending;Haskell 中,你不告诉我什么时候调用这个值,我就维持 thunk 状态; 无限列表 在 Haskell...不断递增数组; 为什么Haskell 中行,在 JavaScript 中不行?

    64620

    当我们谈论Monad时候(二)

    但是如果按照这个方法,我们对每一个数量参数都需要写一个liftM*函数,非常麻烦。而对于容器外面的普通函数,我们就不会遇到这个问题,因为函数都是柯里化。所以,为什么不把柯里化引入Functor呢?...Haskell中全符号、被小括号包裹函数默认是中缀,比如这个函数调用就是中缀形式f xs。接受一个容器内函数和值,并将运算之后结果重新放在容器中。...不过,这也只解释了为什么如今HaskellApplicative和Monad是这种状态。那么,是什么原因使Haskell冒着把标准库搞乱风险也要引入Applicative呢?...在调用形式上看,>>=左侧是之前运算结果,而右侧通过λ参数将这个结果引入了进来,以供之后使用。但是左侧与右侧并没有联系,因此之后运算是无法依赖于之前运算。...,在形式上,这个x是由λ函数参数引入 [1..3] >>= \x -> [1..x] >>= \y -> return (x + y) 不过这个例子并不能完全说明“不能用之前计算结果

    80910

    ✨从延迟处理讲起,JavaScript 也能惰性编程?

    addA(7) 函数,它说:我并不会执行运算,而会返回给你一个新函数,以及一个“闭包”,这个闭包里面是被引用变量值。...,我们不能保证一直写出不带副作用函数,HTTP 操作/ IO 操作/ DOM 操作等这些行为是业务场景必做,于是想了个法子:用一个“盒子”把不纯函数包裹住,然后一个盒子连着一个盒子声明调用关系,直到最后执行...result2; } else { let result3 = longCalculation3(a,c); return result3; } } } 优化这个写法在逻辑上更合理...因为 JavaScript 本身不是惰性求值语言,它和比如 C 语言这类主流语言一样,是【及早求值】,惰性求值语言有比如 Haskell 这类纯粹函数式编程语言,用 Haskell 实现上述函数为:...Generator Thunk Generator 就像是 Haskell thunk,赋值时候,我不进行计算,把你包装成一个  暂停等待,等你调用 next() 时候,

    66220

    Kotlin版图解Functor、Applicative与Monad

    与从 Swift 版翻译而来 Kotlin 版不同是,本文是直接从 Haskell 版原文翻译而来。 这是一个简单值: ? 我们也知道如何将一个函数应用到这个值上: ? 这很简单。...这里有 Applicative 能做到而 Functor 不能做到事情。 如何将一个接受两个参数函数应用到两个已包装值上?...这究竟是什么意思,这个函数为什么包装在 JUST 中?...Monad 将一个返回已包装值函数应用到一个已包装值上。 Monad 有一个函数 ))=(在 Haskell 中是 >>=,读作“绑定”)来做这个。 让我们来看个示例。...现在你已经通过这篇指南润湿了你口哨,为什么不拉上 Mel Gibson 并抓住整个瓶子呢。 请参阅《Haskell 趣学指南》《来看看几种 Monad》。

    1.2K20

    成为函数式编程工程师四年,我为什么说它既“流氓”又“可爱”

    如今,没有哪种新发布编程语言不支持“函数式编程”,甚至保守温和、经过企业认证 Java 也开始有了 lambdas 甚至 monads。 是的,这是一个全新世界。 为什么转向函数式编程?...这些原因包括: 高阶函数(让你把函数传递给函数,或从函数中返回函数)帮助你在程序中剔除很多重复内容。...需要手动否定布尔谓词< p,这代表了重复信息内容)。...FP 并不能保护我们。我们需要另一种标准来衡量“好代码“,而不是简单地认为“函数式“就是好代码。 我认为这个标准与可组合性、可理解性和正确性有很大关系。...我们不能因为自己写出来“纯函数式“代码就拍拍屁股走人。我们不能忽视“非函数式化“编程技术,包括逻辑编程和响应式编程等等一大堆范式。

    34120

    基础语法_Haskell笔记1

    Haskell特点: 变量不可变:函数式里变量与常量概念一样,源自数学思维,令x=1,那么x永远都是1 引用透明:函数调用能被直接替换成相应值,而不会影响函数行为。...语法格式 Haskell函数调用默认是前缀语法,例如: succ 2 min 1 (-2) 与Bash脚本函数调用语法一样,函数名 参数1 参数2 但运算符作为特殊函数,默认要以中缀形式调用,...区别是目的不同,偏函数应用是为了减少函数所需参数数量(通过固定一些参数值),柯里化是为了把一个多参函数转换成单参函数这个单参函数返回另一个单参函数(参数数量不足),或者求值(参数数量够了) 四.函数声明...,函数名加空格分隔参数列表,=后面是函数体 2个特点: 声明顺序无所谓 函数名首字母不能大写,不能数字开头 P.S.数学里把相似的东西用x x' x''命名习惯表示,在Haskell里也可以这样做:...> take 3 [1..] [1,2,3] -- 或者cycle函数无限重复 > take 7 (cycle [1..3]) [1,2,3,1,2,3,1] -- 或者repeat生成单元素无限重复

    1.9K30

    C++17,标准库新引入并行算法

    A short detour C++17 新引入算法在纯函数式语言 Haskell 中都有对应方法. for_each_n 对应方法为 map. exclusive_scan 和 inclusive_scan...我想你也许好奇为什么我要在介绍C++文章中写这么多 Haskell 内容(这些内容还颇具挑战性),那是因为两个原因: 你可以知道 C++ 中相应算法历史 比照 Haskell 对应方法可以帮助我们理解...所执行操作很类似,其中第一步 lambda 函数将元素映射为了元素长度,对应 Haskell 表达式为: scanl1 (+) . map(\a -> length a) $ strings...现在,代码中 reduce 函数 (9) 看起来就比较简单了,他需要在各个(字符串)元素之间放置 “:” 字符.因为结果开头不能带有 “:” 字符, reduce 迭代是从第二个元素开始(strVec2...Final remarks C++17 新引入这7个算法有很多重载版本,调用时候,你可以指定初始元素,也可以不指定初始元素,同样,你可以指定执行策略,也可以不指定执行策略.你甚至可以在不指定二元运算情况下调用需要二元运算算法

    1.1K20

    泛型和元编程模型:Java, Go, Rust, Swift, D等

    这种方式虽然被Haskell类型类使用,但GHC(GHC是Haskell编译器)通过内联和特殊化,也可以做单态化优化。...我不知道有什么语言使用这种技术,但是C++编译器和Java虚拟机在使用profile-guided优化来了解某个通用调用点主要作用于某些类型对象时,会做类似的事情。...例如如果在日志调用中使用了一个封装函数宏,而在封装函数实现中出错,编译器错误将直接指向错误所在代码,而非指向宏。...这一方式也让Swift编译器和HaskellGHC等编译器即使默认使用装箱来实现泛型,也可以单态化作为优化手段。 机器码单态化 单态化泛型下一步是在编译器后端中进一步推进。...其缺点是每个单态化副本不能优化器特别优化,然而因为没有重复优化,所以编译速度可以快很多。

    3.1K30
    领券