关于haskell:Lab-Exercises-for-COMP26020-Part-2

Lab Exercises for COMP26020 Part 2: Functional Programming in HaskellJoe Razavi December 8, 2023The deadline for this lab is 6pm on 16/2/2024.This lab has three exercises, for a total of ten marks. The first two exercises together are worth eight marks, and I advise all students to focus exclusively on these exercises. Seven marks are given based on automated testing, and one is reserved for human judgement by the marker. These exercises are described in Section 1 below. Section 2 contains submission information and a checklist of tasks for the first two exercises.If you are certain that your solutions are completely correct you might like to look at Section 3 below, which describes a thought-provoking, open-ended exercise requiring significant creativity, worth two marks. It is designed to be extremely difficult, and is not a practical way of gaining marks!1 Simple QuadtreesThis lab exercise concerns as data structure called a ‘quadtree’ which can be used to represent an image. There are sophisticated versions of the quadtree data structure, but for the purposes of the lab we will use a very simple version of the idea.Suppose we want to represent a square, black and white bitmap image which is2n by2n pixels. Theusualwaytodothisisasa2n by2n gridofbits,but this can be wasteful if there are large monochrome areas.1 ...

February 16, 2024 · 16 min · jiezi

关于haskell:Y-分钟速成-Haskell

源代码下载: learn-haskell-zh.hs Haskell 是一门实用的函数式编程语言,因其 Monads 与类型零碎而闻名。而我应用它则是因为它异样优雅。用 Haskell 编程令我感到十分高兴。 -- 单行正文以两个减号结尾{- 多行正文像这样 被一个闭合的块突围-}------------------------------------------------------ 1. 简略的数据类型和操作符------------------------------------------------------ 数字3 -- 3-- 数学计算1 + 1 -- 28 - 1 -- 710 * 2 -- 2035 / 5 -- 7.0-- 默认除法不是整除35 / 4 -- 8.75-- 整除35 `div` 4 -- 8-- 布尔值TrueFalse-- 布尔操作not True -- Falsenot False -- True1 == 1 -- True1 /= 1 -- False1 < 10 -- True-- 在下面的例子中,`not` 是一个承受一个参数的函数。-- Haskell 不须要括号来调用函数,所有的参数都只是在函数名之后列出来-- 因而,通常的函数调用模式是:-- func arg1 arg2 arg3...-- 你能够查看函数局部理解如何自行编写。-- 字符串和字符"This is a string." -- 字符串'a' -- 字符'对于字符串你不能应用单引号。' -- 谬误!-- 连贯字符串"Hello " ++ "world!" -- "Hello world!"-- 一个字符串是一系列字符['H', 'e', 'l', 'l', 'o'] -- "Hello""This is a string" !! 0 -- 'T'------------------------------------------------------ 2. 列表和元组------------------------------------------------------ 一个列表中的每一个元素都必须是雷同的类型。-- 上面两个列表等价[1, 2, 3, 4, 5][1..5]-- 区间也能够这样['A'..'F'] -- "ABCDEF"-- 你能够在区间中指定步进[0,2..10] -- [0, 2, 4, 6, 8, 10][5..1] -- 这样不行,因为 Haskell 默认递增[5,4..1] -- [5, 4, 3, 2, 1]-- 列表下标[0..] !! 5 -- 5-- 在 Haskell 你能够应用有限列表[1..] -- 一个含有所有自然数的列表-- 有限列表的原理是,Haskell 有“惰性求值”。-- 这意味着 Haskell 只在须要时才会计算。-- 所以当你获取列表的第 1000 项元素时,Haskell 会返回给你:[1..] !! 999 -- 1000-- Haskell 计算了列表中第 1 至 1000 项元素,但这个有限列表中剩下的元素还不存在。-- Haskell 只有在须要时才会计算它们。-- 连贯两个列表[1..5] ++ [6..10]-- 往列表头减少元素0:[1..5] -- [0, 1, 2, 3, 4, 5]-- 其它列表操作head [1..5] -- 1tail [1..5] -- [2, 3, 4, 5]init [1..5] -- [1, 2, 3, 4]last [1..5] -- 5-- 列表推导 (list comprehension)[x*2 | x <- [1..5]] -- [2, 4, 6, 8, 10]-- 附带条件[x*2 | x <-[1..5], x*2 > 4] -- [6, 8, 10]-- 元组中的每一个元素能够是不同类型,然而一个元组的长度是固定的-- 一个元组("haskell", 1)-- 获取元组中的元素(例如,一个含有 2 个元素的元祖)fst ("haskell", 1) -- "haskell"snd ("haskell", 1) -- 1------------------------------------------------------ 3. 函数------------------------------------------------------ 一个承受两个变量的简略函数add a b = a + b-- 留神,如果你应用 ghci (Haskell 解释器),你须要应用 `let`,也就是-- let add a b = a + b-- 调用函数add 1 2 -- 3-- 你也能够应用反引号中置函数名:1 `add` 2 -- 3-- 你也能够定义不带字母的函数名,这样你能够定义本人的操作符。-- 这里有一个做整除的操作符(//) a b = a `div` b35 // 4 -- 8-- Guard:一个在函数中做条件判断的简略办法fib x | x < 2 = x | otherwise = fib (x - 1) + fib (x - 2)-- 模式匹配与 Guard 相似。-- 这里给出了三个不同的 fib 定义。-- Haskell 会主动调用第一个合乎参数模式的申明fib 1 = 1fib 2 = 2fib x = fib (x - 1) + fib (x - 2)-- 元组的模式匹配foo (x, y) = (x + 1, y + 2)-- 列表的模式匹配-- 这里 `x` 是列表中第一个元素,`xs` 是列表残余的局部。-- 咱们能够实现本人的 map 函数:myMap func [] = []myMap func (x:xs) = func x:(myMap func xs)-- 匿名函数带有一个反斜杠,前面跟着所有的参数myMap (\x -> x + 2) [1..5] -- [3, 4, 5, 6, 7]-- 在 fold(在一些语言称 为`inject`)中应用匿名函数-- foldl1 意味着左折叠 (fold left), 并且应用列表中第一个值作为累加器的初始值。foldl1 (\acc x -> acc + x) [1..5] -- 15------------------------------------------------------ 4. 其它函数------------------------------------------------------ 局部调用-- 如果你调用函数时没有给出所有参数,它就被“局部调用”。-- 它将返回一个承受余下参数的函数。add a b = a + bfoo = add 10 -- foo 当初是一个承受一个数并对其加 10 的函数foo 5 -- 15-- 另一种等价写法foo = (+10)foo 5 -- 15-- 函列表合-- (.) 函数把其它函数链接到一起。-- 例如,这里 foo 是一个承受一个值的函数。-- 它对承受的值加 10,并对后果乘以 5,之后返回最初的值。foo = (*5) . (+10)-- (5 + 10) * 5 = 75foo 5 -- 75-- 修改优先级-- Haskell 有另外一个函数 `$` 能够扭转优先级。-- `$` 使得 Haskell 先计算其左边的局部,而后调用右边的局部。-- 你能够应用 `$` 来移除多余的括号。-- 批改前(even (fib 7)) -- False-- 批改后even . fib $ 7 -- False-- 等价地even $ fib 7 -- False------------------------------------------------------ 5. 类型申明------------------------------------------------------ Haskell 有一个十分弱小的类型零碎,所有都有一个类型申明。-- 一些根本的类型:5 :: Integer"hello" :: StringTrue :: Bool-- 函数也有类型-- `not` 承受一个布尔型返回一个布尔型-- not :: Bool -> Bool-- 这是承受两个参数的函数-- add :: Integer -> Integer -> Integer-- 当你定义一个值,申明其类型是一个好做法double :: Integer -> Integerdouble x = x * 2------------------------------------------------------ 6. 控制流和 If 语句------------------------------------------------------ if 语句:haskell = if 1 == 1 then "awesome" else "awful" -- haskell = "awesome"-- if 语句也能够有多行,留神缩进:haskell = if 1 == 1 then "awesome" else "awful"-- case 语句-- 解析命令行参数:case args of "help" -> printHelp "start" -> startProgram _ -> putStrLn "bad args"-- Haskell 没有循环,它应用递归-- map 对一个列表中的每一个元素调用一个函数map (*2) [1..5] -- [2, 4, 6, 8, 10]-- 你能够应用 map 来编写 for 函数for array func = map func array-- 调用for [0..5] $ \i -> show i-- 咱们也能够像这样写for [0..5] show-- 你能够应用 foldl 或者 foldr 来合成列表-- foldl <fn> <initial value> <list>foldl (\x y -> 2*x + y) 4 [1,2,3] -- 43-- 等价于(2 * (2 * (2 * 4 + 1) + 2) + 3)-- foldl 从左开始,foldr 从右foldr (\x y -> 2*x + y) 4 [1,2,3] -- 16-- 当初它等价于(2 * 3 + (2 * 2 + (2 * 1 + 4)))------------------------------------------------------ 7. 数据类型------------------------------------------------------ 在 Haskell 中申明你本人的数据类型:data Color = Red | Blue | Green-- 当初你能够在函数中应用它:say :: Color -> Stringsay Red = "You are Red!"say Blue = "You are Blue!"say Green = "You are Green!"-- 你的数据类型也能够有参数:data Maybe a = Nothing | Just a-- 这些都是 Maybe 类型:Just "hello" -- `Maybe String` 类型Just 1 -- `Maybe Int` 类型Nothing -- 对任意 `a` 为 `Maybe a` 类型------------------------------------------------------ 8. Haskell IO------------------------------------------------------ 尽管不解释 Monads 就无奈齐全解释 IO,但大抵理解并不难。-- 当执行一个 Haskell 程序时,函数 `main` 就被调用。-- 它必须返回一个类型 `IO ()` 的值。例如:main :: IO ()main = putStrLn $ "Hello, sky! " ++ (say Blue) -- putStrLn 的类型是 String -> IO ()-- 如果你的程序输出 String 返回 String,那样编写 IO 是最简略的。-- 函数-- interact :: (String -> String) -> IO ()-- 输出一些文本,对其调用一个函数,并打印输出。countLines :: String -> StringcountLines = show . length . linesmain' = interact countLines-- 你能够认为一个 `IO ()` 类型的值是示意计算机做的一系列操作,相似命令式语言。-- 咱们能够应用 `do` 申明来把动作连贯到一起。-- 举个列子sayHello :: IO ()sayHello = do putStrLn "What is your name?" name <- getLine -- 这里承受一行输出并绑定至 "name" putStrLn $ "Hello, " ++ name -- 练习:编写只读取一行输出的 `interact` -- 然而,`sayHello` 中的代码将不会被执行。惟一被执行的动作是 `main` 的值。-- 为了运行 `sayHello`,正文下面 `main` 的定义,替换为:-- main = sayHello-- 让咱们来更进一步了解方才所应用的函数 `getLine` 是怎么工作的。它的类型是:-- getLine :: IO String-- 你能够认为一个 `IO a` 类型的值代表了一个运行时会生成一个 `a` 类型值的程序。-- (可能随同其它行为)-- 咱们能够通过 `<-` 保留和重用这个值。-- 咱们也能够实现本人的 `IO String` 类型函数:action :: IO Stringaction = do putStrLn "This is a line. Duh" input1 <- getLine input2 <- getLine -- `do` 语句的类型是它的最初一行 -- `return` 不是关键字,只是一个一般函数 return (input1 ++ "\n" ++ input2) -- return :: String -> IO String-- 咱们能够像调用 `getLine` 一样调用它main'' = do putStrLn "I will echo two lines!" result <- action putStrLn result putStrLn "This was all, folks!"-- `IO` 类型是一个 "Monad" 的例子。-- Haskell 通过应用 Monad 使得其自身为纯函数式语言。-- 任何与外界交互的函数(即 IO)都在它的类型申明中标记为 `IO`。-- 这通知咱们什么样的函数是“纯净的”(不与外界交互,不批改状态) ,-- 什么样的函数不是 “纯净的”。-- 这个性能十分弱小,因为纯函数并发非常容易,由此在 Haskell 中做并发非常容易。------------------------------------------------------ 9. Haskell REPL------------------------------------------------------ 键入 `ghci` 开始 REPL。-- 当初你能够键入 Haskell 代码。-- 任何新值都须要通过 `let` 来创立let foo = 5-- 你能够通过命令 `:t` 查看任何值的类型>:t foofoo :: Integer-- 你也能够运行任何 `IO ()`类型的动作> sayHelloWhat is your name?Friend!Hello, Friend!Haskell 还有许多内容,包含类型类 (typeclasses) 与 Monads。这些都是令 Haskell 编程十分乏味的好货色。咱们最初给出 Haskell 的一个例子,一个疾速排序的实现: ...

November 28, 2022 · 5 min · jiezi

关于haskell:Haskell-Monoid幺半群的介绍

翻译自 https://gist.github.com/cscal...为什么程序员应该关怀 Monoids?因为 Monoids 是一种在编程中重复呈现的常见模式。当模式呈现时,咱们能够将它们抽象化并利用咱们过来所做的工作。这使咱们可能在通过验证的稳固代码之上疾速开发解决方案。 将"可替换性"增加到 Monoid(Commutative Monoid),你就有了能够并行执行的货色。随着摩尔定律的终结,并行计算是咱们进步处理速度的惟一心愿。 以下是我在学习 Monoids 后学到的。它未必残缺,但心愿可能对于向人们介绍 Monoids 有所帮忙。 Monoid 谱系Monoid 来自数学,从属于代数构造的谱系。因而,从头开始并逐渐开展到 Monoids 会有所帮忙。 实际上,咱们进一步能够推到"群"(Groups). Magma(元群)Magma 是一个汇合以及一个必须闭合的二元运算: ∀ a, b ∈ M : a • b ∈ M如果将二元运算利用于汇合的任意 2 个元素时,它会生成汇合的另一个成员,则该二元运算是关闭的。 (这里 · 示意二元运算) Magma 的一个示例是 Boolean 和 AND 运算的汇合。 Semigroup(半群)Semigroup 是具备一个附加要求的 Magma。二元运算对于汇合的所有成员必须是"可联合"的: ∀ a, b, c ∈ S : a · (b · c) = (a · b) · c一个 Semigroup 的例子是"非空字符串"和"字符串拼接"运算的汇合。 Monoid(幺半群)Monoid 是蕴含一个附加条件的 Semigroup。汇合中存在一个"幺元"(Neutral Element),能够应用二元运算将其与汇合的任何成员联合,而产生属于雷同汇合的成员。 ...

April 21, 2022 · 8 min · jiezi

关于haskell:Expression-Problem-和-Calcit-相关引用笔记

Wiki https://en.wikipedia.org/wiki...知乎援用 https://www.zhihu.com/questio...中文简介 http://mgampkay.github.io/pos...The Expression Problem and its solutionsMore thoughts on the Expression Problem in Haskell3 ways to solve the expression problemThe Expression Problem in RustSolving the Expression Problem with Clojure(Protocol)Calcit 示例代码 ns app.maindefrecord %expr-methods :evaldef %const $ %{} %expr-methods :eval $ fn (tp) nth tp 1def %binary-plus $ %{} %expr-methods :eval $ fn (tp) &let pair $ nth tp 1 + (nth pair 0) (nth pair 1)def %const-2 $ .extend-as %const '%const-2 , :stringify $ fn (tp) str "|(Const " (nth tp 1) "| )"def %binary-plus-2 $ .extend-as %binary-plus '%binary-plus-2' , :stringify $ fn (tp) &let pair $ nth tp 1 str "|(BinaryPlus " (first pair) "| " (last pair) "| )"defn main () echo $ .eval $ :: %const 1 echo $ .eval $ :: %binary-plus $ [] 1 2 echo $ .stringify $ :: %const-2 1 echo $ .eval $ :: %const-2 1 echo $ .stringify $ :: %binary-plus-2 $ [] 1 2 echo $ .eval $ :: %binary-plus-2 $ [] 1 2运行示例: ...

August 10, 2021 · 1 min · jiezi

关于haskell:一种-Monad-的偏门的理解方式

我对数学概念属性符号把握得不好, 所以了解比较慢,这篇文章是从概念性的内容去了解 Monad, 不准确, 可能具体到数学概念也不精确.然而心愿提供一个比拟直观的形式去理解, Monad 是怎么来的? 概念上简略说 Monad 是"自函子领域上的一个幺半群".新概念很多, 函子, 自函子, 领域, 半群, 幺半群.模糊地讲, 这些就是数学的概念, 汇合啦, 汇合元素间的映射啦, 单位元啦, Monad 概念含糊了解的话, 函子能够当做是函数, a -> b 的映射, 当然也能够比函数更形象点的货色,而后"自函子", 波及到类型的概念, 函子从一个汇合 A 到另一个汇合 B,但咱们把程序所有货色都放在一起的话, 函子能够认为是从 A 到 A 本人了, 所以是"自函子"."领域"我解释不来, 大抵是这些函子的形成和关系, 具体看何幻的文章.从后面这部分看, Haskell 把程序当成各种汇合还有映射来对待了,程序中, 无论值的变动, 甚至副作用的变动, 全都纳入到领域里边来了解. 而后幺半群呢? 要了解这些概念, 就要晓得相干的几个概念, 原群(Magma)一个汇合, 而后汇合上的元素的二元操作, 操作后果都在这个汇合外部,一个例子, 比方 { true false }, 还有二元操作 and or,任何二元操作的后果都在汇合内. 半群(Semigroup)半群在原群的根底上减少了一个条件, 满足结合律:比方所有非空的字符串的汇合, 以及 concat 操作."a" `concat` "b" 失去 "ab" 还在汇合内,而后 ("a" `concat` "b") `concat` "c" 失去 "abc",而后 "a" `concat` ("b" `concat` "c") 失去 "abc",两者是等价的, 满足结合律. ...

March 16, 2021 · 3 min · jiezi

Haskell编程解决九连环3-详细的步骤

摘要在本系列的上一篇文章《Haskell编程解决九连环(2)— 多少步骤?》中,我们通过编写Python和Haskell的代码解决了关于拆解九连环最少需要多少步的问题。在本文中我们将更进一步,输出所有的详细步骤。每个步骤实际上是一个装上或者拆下一个圆环的动作。关于步骤动作的定义请参见本系列的第一篇文章《Haskell编程解决九连环(1)— 数学建模》。维基百科上关于九连环的条目中有拆解n连环所需的步数,在本文中我们将要通过编程计算来得出与下表中数字相对应的详细步骤动作,特别的,当连环的数目n=9时,结果应该是341条动作。 连环的数目123456789步数12510214285170341定理与推论定理与推论是我们编程实现的基础和指导,再次将它们罗列如下。 定理1:takeOff(1)的解法步骤序列为[OFF 1],putOn(1)的解法步骤序列为[ON 1]。定理2:takeOff(2)的解法步骤序列为[OFF 2, OFF 1],putOn(2)的解法步骤序列为[ON 1, ON 2]。定理3:当n>2时,takeOff(n)的解法由以下步骤组成:1) takeOff(n-2) 2) OFF n 3) putOn(n-2) 4) takeOff(n-1);而putOn(n) 由以下步骤组成 1) putOn(n-1) 2) takeOff(n-2) 3) ON n 4) putOn(n-2)。推论1:takeOff(n)的解法步骤序列和putOn(n)的解法步骤序列互为逆反序列。推论2:takeOff(n)的解法步骤序列和putOn(n)的解法步骤序列含有的步骤数目相等。推论3:对于任何整数m, n,如果m>n,那么第m环的状态(装上或是卸下)不影响takeOff(n)或者putOn(n)的解,同时解决takeOff(n)或者putOn(n)问题也不会改变第m环的状态。 照例,我们从一个命令式语言的实现开始。这有助于熟悉命令式编程语言的小伙伴们理解。也为随后的的Haskell实现设定结果规范。 Python 实现因为涉及到大量的输入输出,这次我们试着构造一个完整的程序。在该程序中提供了主函数入口,使得下面的代码不仅能在交互式环境中运行和测试,也可以在操作系统的命令行环境直接调用。 #!/usr/bin/pythonimport itertoolsdef printAction(stepNo, act, n): # (1) print('{:d}: {:s} {:d}'.format(stepNo, act, n))def takeOff(n, stepCount): # (2) if n == 1: printAction(next(stepCount), 'OFF', 1) elif n == 2: printAction(next(stepCount), 'OFF', 2) printAction(next(stepCount), 'OFF', 1) else: takeOff(n - 2, stepCount) printAction(next(stepCount), 'OFF', n) putOn(n - 2, stepCount) takeOff(n - 1, stepCount) def putOn(n, stepCount): if n == 1: printAction(next(stepCount), 'ON', 1) elif n == 2: printAction(next(stepCount), 'ON', 1) printAction(next(stepCount), 'ON', 2) else: putOn(n - 2, stepCount) printAction(next(stepCount), 'ON', n) takeOff(n - 2, stepCount) putOn(n - 1, stepCount)if __name__ == "__main__": # (3) n = int(input()) # (4) takeOff(n, itertools.count(start = 1)) # (5)这里我们不再赘述关于递归的基本条件或者递归的拆分算法部分。有兴趣的读者可以参阅本系列的前两篇文章。有一些新出现的东西值得关注。首先命令式语言并不禁止任何函数具有副作用,我们能很容易地在任何地方做输入输出。在代码中大家也可以看到函数的申明没有任何的区别,但是我们能够在迭代或是递归的过程中打印相应的结果。下面我们沿着在代码中标注的序号做一些解释。 ...

September 10, 2019 · 7 min · jiezi

GTKGTK介绍

最近在用GTK写一些工具,所以写一个基础教程系列,总结学习成果。 简介GTK是一款开源的、面向多平台的GUI工具箱,其英文全称为GIMP Toolkit。最初是Peter Mattis 和 Spencer Kimball 为GNU Image Manipulation Program (GIMP)编写,用来替代付费的Motif。在后续的发展中,它已经成为通用的GUI库,应用于越来越多的程序,Linux平台的图形应用程序的半壁江山都是使用GTK编写的。 GTK的英文全称GTK的英文全称,让我想到了GCC。GCC最初定位于GNU C Compiler,但随着支持的编译器越来越多,它的定义已经包不住编译器的多样性,所以现在改成了GNU Compiler Collection。这样看来,是不是GTK的名字也得换换了,毕竟现有的名字很局限。 GTK的语言绑定GTK是使用C语言写的,所以其原生API都是面向C的,同时GTK的一大特点是,在C语言层面实现了面向对象的特性。如果你是用C++语言作为开发语言、调用GTK的C接口的话,使用会稍显繁琐,这是语言层面的差异,跟框架关系不大。正是为了避免不同语言调用C的繁琐,GTK提供了多语言的绑定,为不同的语言提供同等抽象级别的语言调用,这样C++程序员就可以直接调用C++的语言绑定,使用方式友好。 GTK的授权GTK是完全免费的,而且基于LGPL协议,这可以保证私有软件通过链接使用GTK可以不把软件源代码开放,对商业应用较友好,这跟GPL协议是不一样的。也正是LGPL协议,使得早些年Gnome(基于GTK编写)风头胜过KDE(基于QT编写)。 GTK的跨平台GTK是跨平台的,支持Unix类的系统、Windows,甚至手机平台。之前我专门有篇文章介绍了在Windows下的环境搭建,C语言的开发环境的搭建还是非常容易的。 GTK vs GTK+关于名字。从网上的资料上,你可以看到GTK+的字眼,这个加号官方是有描述的: The "plus" was added to "GTK" once it was moved out of the GIMP sources tree and the project gained utilities like GLib and the GTK type system, in order to distinguish it from the previous, in-tree version.大意是:GTK从GIMP独立出来之后,加入了一些GLib和GTK类型系统的支持,为了和GIMP代码树中的版本区分,所以带上加号,这一区分就是好多年,给广大的人民群众带来了不小的认知麻烦。在今年,官方终于决定把加号去掉,以后直接叫GTK。 GTK的发布版本关于版本。现在开源的大环境是采用刷版本的方式,像火狐浏览器,谷歌浏览器版本蹭蹭的涨。之前GTK一直采用小步慢跑的版本方式,估计也快要刷版本了,下面引用一篇旧闻: GNOME开发者在多伦多举办的GTK会议上讨论了新的Gtk发布方案,针对Gtk 3.x系列中的问题,开发者提议加快大版本的发布速度:每两年发布一个大版本如 Gtk 4、Gtk 5和Gtk 6,每6个月发布一个与旧版本不兼容的小版本,如Gtk 4.2、Gtk 4.4和Gtk 4.6。这项计划意味着Gtk 4.0不是我们将称之为Gtk 4的最终稳定API。新的大版本能与旧的版本并行安装,如Gtk 4 和Gtk 3能安装在一个系统中,但不兼容的小版本不能,它们使用了相同的pkg-config名字和头文件目录。每一个连续小版本的API将逐渐成熟稳定,也就是说Gtk 4.6发布时API将最终稳定下来,Gtk 4.6可以称之为 Gtk 4了。使用Gtk的开发者可以选择跟随稳定的版本。为什么选择GTK免费这条最实在。大的组织,比如公司,也是很注重成本的;小的个人,财务的承受能力也是有限的,这是GTK的诞生的原因。而且,很多软件授权真的不便宜。 ...

June 27, 2019 · 1 min · jiezi

GTKWindows下Haskell的GTK开发环境搭建

一个在Windows下相对小众的GUI框架-GTK,碰上一个相对小众的编程语言-Haskell,会碰出什么样的火花呢?现实的结果就是:相对于Linux,想在Windows下搭建好一个能工作的开发环境,简直是太难了!本篇文章介绍了一种可用的搭建方法。 安装msys2安装过程比较简单,过程略。假设安装的是64位的版本:msys2_x86_64,使用默认配置安装,默认C盘。 配置环境变量可在当前命令行里配置环境变量,如下: SET PATH=C:\\msys64\\mingw64\\bin;C:\\msys64\\usr\\bin;%PATH%SET PKG_CONFIG_PATH=C:\\msys64\mingw64\\lib\\pkgconfigSET XDG_DATA_DIRS=C:\\msys64\\mingw64\\share上面环境变量只是在当前会话生效;若要永久生效则需: 安装GTK软件包安装完msys2后,启动菜单里会就会有如图所示的三个菜单项: 启动msys2或mingw64任意一个,执行以下命令安装一些相关的函数库和工具: pacman -S mingw-w64-x86_64-gtk3pacman -S mingw-w64-x86_64-gladepacman -S mingw-w64-x86_64-toolchain base-develpacman -S mingw-w64-x86_64-gobject-introspectionpacman -S mingw-w64-x86_64-gtksourceview3上述软件,比如glade,早期编程可能用不到,也可以暂时不安装。如果后续还少其他软件包,使用pacman安装即可。 到这里,C语言的GTK开发环境已经搭建好了,可使用C语言编写GTK程序了。下面是使用C语言编写的一个示例程序example.c: #include <gtk/gtk.h>static voidactivate (GtkApplication* app, gpointer user_data){ GtkWidget *window; window = gtk_application_window_new (app); gtk_window_set_title (GTK_WINDOW (window), "Window"); gtk_window_set_default_size (GTK_WINDOW (window), 200, 200); gtk_widget_show_all (window);}intmain (int argc, char **argv){ GtkApplication *app; int status; app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE); g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); status = g_application_run (G_APPLICATION (app), argc, argv); g_object_unref (app); return status;}使用以下命令编译: ...

June 10, 2019 · 1 min · jiezi

【算法】算法图解笔记_快速排序

分而治之分而治之(divide and conquer,D&C)是一种著名的递归式问题解决方法。只能解决一种问题的算法毕竟用处有限,而D&C提供了解决问题的思路,是另一个可供你使用的工具。D&C算法是递归的。使用D&C解决问题的过程包括两个步骤。(1) 找出基线条件,这种条件必须尽可能简单。(2) 不断将问题分解(或者说缩小规模),直到符合基线条件。例1假设你是农场主,有一小块土地。如何将一块地均匀地分成方块,并确保分出的方块是最大的呢?基线条件最容易处理的情况是,一条边的长度是另一条边的整数倍。比如,25m x 50m的土地可以分成 2 个 25m x 25m 的方块。递归条件根据D&C的定义,每次递归调用都必须缩小问题的规模。首先找出这块地可容纳的最大方块。如,上图可以划出两个640 m × 640 m的方块,同时余下一小块400m x 640m 地。我们下一步要做的就是对余下的那一小块地使用相同的算法。因为适用于这小块地的最大方块,也是适用于整块地的最大方块。换言之,你将均匀划分1680 m × 640 m土地的问题,简化成了均匀划分400m x 640 m土地的问题!我们很容易实现上述过程。我们进一步抽象,这个过程实际上就是求两个整数的最大公倍数。例2给定一个数字数组,如,[2,4,6],怎么返回这些数字相加后的结果。使用循环可以很容易实现。那使用递归怎么实现呢?基线条件最简单的数组不包含任何元素或只包含一个元素,这个可以认为是数组的基线条件。递归条件每次递归调用都必须离空数组更近一步。我们通过下面的等式缩小问题的规模。sum [2,4,6] == 2 + sum [4,6]使用Haskell可以很容易实现:sum [] = 0sum (x:xs) = x + (sum xs)快速排序快速排序是一种常用的排序算法,如,C语言标准库中的函数qsort实现的就是快速排序。基线条件数组为空或只包含一个元素。在这种情况下,只需原样返回数组。递归条件我们从数组中选择一个元素作为基准值(pivot),然后以该值为基准对数据分区(partitioning),这样数组划分成了三部分:一个由所有小于基准值的数字组成的子数组;基准值一个由所有大于基准值的数组组成的子数组。这样问题缩小到了子数组规模。再分别对子数组应用以上过程,得到排序后的子数组,最终我们只要将这三部分拼接起来就能得到完全排序的数组。注意:为了实现简单,基准值每次都取的数组首元素。代码如下:# pythondef quicksort(array): if len(array) < 2: return array else: pivot = array[0] less = [i for i in array[1:] if i <= pivot] greater = [i for i in array[1:] if i > pivot] return quicksort(less) + [pivot] + quicksort(greater)–haskellimport Data.ListquickSort :: Ord a => [a] -> [a]quickSort [] = []quickSort (x:xs) = quickSort lhs ++ [x] ++ quickSort rhs where (lhs, rhs) = partition (< x) xs注意:上面的版本每次都新生成子数组,有些人认为正确的快速排序应该使用in-place交换,所以上面的算法不“正宗”。再谈大 O 表示法快速排序的独特之处在于,其速度取决于选择的基准值。在平均情况下,快速排序的运行时间为O(nlog n),在最糟情况下,退化为O(n2)。还有一种合并排序(merge sort)的排序算法,其运行时间为O(nlogn)。大O表示法体现出的是对元素规模n的增速,但处理每个元素的速度是有差异的,比如,对每个元素执行(*2)和(+3).(*2)操作,明显是后者执行的时间长。快速排序和合并排序的算法速度分别表示为c1 nlogn和c2 nlogn,c是算法所需的固定时间量,被称为常量。通常不考虑这个常量,因为如果两种算法的大O运行时间不同,这种常量将无关紧要。但有时候,常量的影响可能很大,对快速查找和合并查找来说就是如此。快速查找的常量比合并查找小,因此如果它们的运行时间都为O(n log n),快速查找的速度将更快。实际上,快速查找的速度确实更快,因为相对于遇上最糟情况,它遇上平均情况的可能性要大得多。 ...

March 31, 2019 · 1 min · jiezi

【算法】递归应用_常见算法的递归实现

前面学习了递归,趁热打铁就把常见的一些算法用递归实现了一遍,找找递归的感觉。斐波那契数列用递归函数定义如下:(1) n = 0时,f(n) = 0(2) n = 1时,f(n) = 1(3) n > 1时,f(n) = f(n-1) + f(n-2)–Haskellfibonacci :: (Num a, Eq a) => a -> afibonacci 0 = 0fibonacci 1 = 1fibonacci n = fibonacci (n - 1) + fibonacci (n - 2)这代码几乎就是上面的递归定义原封不动的“照搬”。当然这个版本性能并不是太高,优化的空间很大,这里不讨论。插入排序大部分人在打牌的时候无意中就使用了这个算法。左手拿着排序好的排【一开始没有牌,当然满足条件】,假设按升序排列,每次右手摸完牌都会插入到左手适当的位置来保持左手中的牌是有序的。人的话,可能并没有意识到,实际上你在插入牌的时候,是通过将右手的牌或者从头开始或从尾开始依次跟左手的牌对比,然后找到合适的位置。时间复杂度为O(n2)。我们可以递归定义如下:(1)如果列表内没有元素,我们就返回空列表(2)如果列表不为空,对列表元素的排序,可以认为就是将列表首元素插入到已经排好序的其他元素中,这些元素的排序通过插入排序实现。代码如下:–HaskellinsertionSort :: Ord a => [a] -> [a]insertionSort [] = []insertionSort (x:xs) = insert x $ insertionSort xsinsert :: Ord a => a -> [a] -> [a]insert x y = let (p,q) = span (<x) y in p ++ [x] ++ q冒泡排序这个算法也较为直观,假设升序排列,第一次遍历会将最大的元素移动到列表的右侧,怎么移动的呢?只要从左到右,依次比较当前位置的元素和右侧的元素,交换顺序不对的两个元素的位置。通过多次遍历,最终得到所有元素有序的列表。时间复杂度为O(n2)。–HaskellbubbleSort :: Ord a => [a] -> [a]bubbleSort [] = []bubbleSort x = bubbleSort initx ++ [lastx] where x’ = swap’ x initx = init x’ lastx = last x’swap’ :: Ord a => [a] -> [a]swap’ [] = []swap’ [x] = [x]swap’ (x1:x2:xs) | x1 > x2 = x2 : swap’ (x1 : xs) | otherwise = x1 : swap’ (x2 : xs)归并排序这个算法的效率较高,时间复杂度为O(nlogn),采用了分治思想,即:将元素分为两部分,每一部分分别采用归并排序算法排序,然后将这两部分已排序的部分合并为整体有序。–HaskellmergeSort :: Ord a => [a] -> [a]mergeSort [] = []mergeSort [x] = [x]mergeSort xs = merge (mergeSort x1) (mergeSort x2) where (x1, x2) = splitAt half xs half = (length xs) div 2merge :: Ord a => [a] -> [a] -> [a]merge [] ys = ysmerge xs [] = xsmerge x1@(x:xs) y1@(y:ys) | x > y = y : merge x1 ys | otherwise = x : merge xs y1二分算法在前面实现了一版,如下–Haskellimport qualified Data.Vector as VbinarySearch :: (Ord a)=> V.Vector a -> Int -> Int -> a -> Maybe IntbinarySearch vec low high e | low > high = Nothing | vec V.! mid < e = binarySearch vec (mid+1) high e | vec V.! mid > e = binarySearch vec low (mid-1) e | otherwise = Just mid where mid = low + ((high-low) div 2)但只能返回一个元素,如果我们想返回所有与e相等的元素位置该怎么办呢?一种非递归的方案可能是:使用上面得到的位置,然后使用循环向前和向后搜索,从而得到与之相等的所有元素的索引。下面是递归的版本,与循环版本对应,只不过在找到相等元素的时候继续往下递归即可。–Haskellimport qualified Data.Vector as VbinarySearch’ :: (Ord a) => V.Vector a -> Int -> Int -> a -> [Int]binarySearch’ vec low high e | low > high = [] | vec V.! mid < e = binarySearch’ vec (mid + 1) high e | vec V.! mid > e = binarySearch’ vec low (mid - 1) e | otherwise = binarySearch’ vec low (mid - 1) e ++ [mid] ++ (binarySearch’ vec (mid + 1) high e) where mid = low + ((high - low) div 2)下一章介绍的快速排序也是递归的范本,下一篇文章介绍。微信公众号 ...

March 26, 2019 · 2 min · jiezi

【算法】算法图解笔记_算法简介

在读《算法图解》这本书,这本书有两个优点:手绘风格的图,看着很让人“入戏”;算法采用Python语言描述,能更好的表达算法思想。关于算法的学习有两点心得:算法思想最重要,理解了思想,算法是很容易写出来的,所以尽量不要把过多精力放在细节上。比如,本书的快速排序,使用了列表推导式,很简单就把算法的思想描述了出来。相比而言,某些使用C语言的书籍给出的版本则比较难懂,归其原因是因为太突出细节了。细节不是不重要,而是我们要首先把算法能更好地理解,下一步才是实现和优化。某些情况下递归比迭代更容易表达算法 递归是描述性的,侧重了对算法性质的表达;而迭代更侧重算法实现,往往掺杂了太多的实现细节。所以,我同时用Haskell给出递归版本的算法描述。好了,开始。引言算法是一组完成任务的指令。任何代码片段都可视为算法。[注意:该书对算法的定义与通行的定义不同,通行的定义要求算法应该具有有穷性,即算法必须能在执行有限个步骤之后终止,否则算法是没有意义的。]在本书中,你将学习比较不同算法的优缺点:该使用合并排序算法还是快速排序算法,或者该使用数组还是链表。仅仅改用不同的数据结构就可能让结果大不相同。二分查找(binary search)其输入是一个有序的元素列表;查找的元素包含在列表中,二分查找返回其位置;否则返回 Nothing。一般而言,对于包含n个元素的列表,用二分查找最多需要log2n步。Python版本:def binary_search(list, item): low = 0 high = len(list)—1 while low <= high: mid = (low + high) guess = list[mid] if guess == item: return mid if guess > item: high = mid - 1 else: low = mid + 1 return NoneHaskell版本:使用了Vector,因为标准库的列表是单链表,不支持随机访问。import qualified Data.Vector as VbinarySearch :: (Ord a)=> V.Vector a -> Int -> Int -> a -> Maybe IntbinarySearch vec low high e | low > high = Nothing | vec V.! mid > e = binarySearch vec (mid+1) high e | vec V.! mid < e = binarySearch vec low (mid-1) e | otherwise = Just mid where mid = low + ((high-low) div 2)大 O 表示法仅知道算法需要多长时间才能运行完毕还不够,还需知道运行时间如何随列表增长而增加。算法的运行时间以不同的速度增加算法的速度指的并非时间,而是操作数的增速。谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么样的速度增加。大 O 表示法让你能够比较操作数,它指出了算法运行时间的增速。大 O 表示法指出了最糟情况下的运行时间这是一个保证——如,简单查找的运行时间不可能超过O(n)。一些常见的大 O 运行时间 O(log n),也叫对数时间,这样的算法包括二分查找。 O(n),也叫线性时间,这样的算法包括简单查找。 O(n * log n),这样的算法包括快速排序——一种速度较快的排序算法。 O(n2),这样的算法包括选择排序——一种速度较慢的排序算法。 O(n!),这样的算法包括旅行商问题的解决方案——一种非常慢的算法旅行商问题旅行商要前往n个城市,同时要确保旅程最短,时间复杂度:O(n!)。请关注我的公众号哦。 ...

March 16, 2019 · 1 min · jiezi

<译>伴随

上一篇:有关态射的一切原文地址:https://bartoszmilewski.com/2…这一篇正在翻译当中…

March 8, 2019 · 1 min · jiezi

<译>有关态射的一切

上一篇:米田嵌入原文地址: https://bartoszmilewski.com/2…如果我还没有使你已经确信范畴论就是所有和态射有关的东西,那就是我的失职。因为下一个主题是伴随,而伴随是用hom集的同构定义的,所以回顾一下有关hom集的那些积木是很有意义的。而且你会看到伴随为描述我们之前研究的很多构造提供了一种更一般的语言,所以复习一下它们也很有必要。函子首先,你其实应该把函子看作态射的映射——这个观点有在Haskell的Functor类型类的定义中得到强调,也就是fmap。当然,函子也映射对象——态射的端点——否则我们就没法谈论保持复合。对象告诉了我们哪些态射对是可复合的。其中一个态射的终点必须等于另一个态射的起点——如果它们能复合。所以如果我们想把态射的复合映为提升后的态射的复合,端点的映射就很大程度上被决定了。交换图态射的很多性质都是用交换图的方式表达的。如果一个特定的态射被以超过一种方式描述为其他态射的复合,那么我们就有一个交换图了。特别地,交换图构成了几乎所有泛构造的基础(初始对象和终端对象是明显的例外)。我们已经在积、余积、很多其他(余)极限、指数对象和自由幺半群等等的定义中看到过它了。积是泛构造的一个简单例子。我们挑选两个对象a和b,看看是否存在一个带上一对态射p和q的具有成为它们积的泛性质的对象c。积是极限的一个具体例子。极限是用锥的观念定义的。泛锥是构建在交换图之上的。这些图表的交换性可以被一个恰当的函子的映射的自然性条件所代替。这种方式下交换性被降为了汇编语言的角色,而高级语言是自然变换。自然变换一般来说,当我们需要从态射映为交换四方图的时候,自然变换会非常方便。自然四方图的两个对边是某个态射f在两个函子F和G作用下的像。另外的边则是自然变换的分量(当然这也是态射)。自然性意味着当你移动到“相邻”分量(相邻的意思是说由一个态射连接)上时,你不会违背范畴的结构,也不会违背这两个函子的。你是先用自然变换的分量桥接对象,再用函子跳到它的邻居上;还是反过来,这一点关系也没有。这两个方向是正交的。自然变换让你左右移动,函子让你上下活着前后移动——打个比方说。你可以设想一个函子的像就是靶范畴里的一页纸。自然变换就把对应于F的这样一页纸映为另一页对应于G的。

February 28, 2019 · 1 min · jiezi

haskell vscode下的环境搭配(包含各种坑的解决办法)

这可能是最傻瓜化的在vscode下的haskell配置介绍文章为什么要写这篇文章?在自己写搭建环境的过程中,搜了一些博文,有些真的及其不服责任和敷衍,草草几句话就带过,但是在google上排名还很高,带着一种鄙视这些文章的态度,于是写下这篇文章,希望给后面的人有帮助安装haskell我的平台:deepin-15.8,基于debian8,(ubuntu等应该这里是一份haskell的学习指南,粗略介绍了安装,资源等一些东西根据上面链接的内容,我们可以得知:不推荐使用Haskell-platform直接安装也不太推荐使用cabel更推荐使用stack安装官网安装文档安装For many Un*x operating systems, all you need to do is run://对于unix类系统curl -sSL https://get.haskellstack.org/ | shor:wget -qO- https://get.haskellstack.org/ | sh(对于windows系统)On Windows, you can download and install the Windows 64-bit Installer.创建你的project:stack new my-projectcd my-projectstack setupstack buildstack exec my-project-exeThe stack new command will create a new directory containing all the needed files to start a project correctly.The stack setup will download the compiler if necessary in an isolated location (default /.stack) that won’t interfere with any system-level installations. (For information on installation paths, please use the stack path command.).The stack build command will build the minimal project.stack exec my-project-exe will execute the command.If you just want to install an executable using stack, then all you have to do is stack install <package-name>.注意,linux系统最好将/.local/bin加入PATH中换源毕竟源在国外,所以我们首先必须要进行换源,幸好清华大学开源网站镜像站有提供,更具体一点可以看Stackage 镜像使用说明,这里记录下vim ~/.stack/config.yaml# addpackage-indices:- name: Tsinghua download-prefix: https://mirrors.tuna.tsinghua.edu.cn/hackage/package/ http: https://mirrors.tuna.tsinghua.edu.cn/hackage/00-index.tar.gzsetup-info: “http://mirrors.tuna.tsinghua.edu.cn/stackage/stack-setup.yaml"urls: latest-snapshot: http://mirrors.tuna.tsinghua.edu.cn/stackage/snapshots.json lts-build-plans: http://mirrors.tuna.tsinghua.edu.cn/stackage/lts-haskell/ nightly-build-plans: http://mirrors.tuna.tsinghua.edu.cn/stackage/stackage-nightly/# 开始使用stack,这个命令需要稍稍等待stack setup# 安装完成之后stack ghci# 会出现以下输出Configuring GHCi with the following packages:GHCi, version 8.0.1: http://www.haskell.org/ghc/ :? for helpLoaded GHCi configuration from /private/var/folders/0s/j3c0tlx10z9_x9wzhl14xmgh0000gn/T/ghci11066/ghci-scriptPrelude>搭建vscode打开vscode,下载extension,这里我推荐这四个插件:Haskell Syntax Highlighting、Haskell ghc-mod 、haskell-linter、Haskelly,其中第四个插件离不开stack。 要想使用以上插件,必须安装以下几个包:# for Haskell ghc-mod stack install ghc-mod# for haskell-linterstack install hlint# for Haskellystack install interostack install QuickCheckstack install stack-run安装时可能出现问题ghc-mod安装时如果报错stackoverflow上的解决办法我采用了stack install ghc-mod –resolver lts-8.24去解决stack-run安装报错github issueI found a workaround.Create a file: ~/.stack/global-project/stack-cabal-1.24.yamlflags: {}extra-package-dbs: []packages: []extra-deps: []resolver: lts-8.24Basically it’s using an old stack lts that was from around the time the last update to stack-run was >made.Then just run this:stack –stack-yaml ~/.stack/global-project/stack-cabal-1.24.yaml install stack-runand it should work (did for me, at least)intero安装报错stack install intero –resolver lts-8.24去解决vscode 插件配置然后打开vscode的配置文件,加上ghc-mod和hlint的路径,如下:“haskell.ghcMod.executablePath”: “/home/.local/bin/ghc-mod”,“haskell.hlint.executablePath”: “/home/.local/bin/hlint” ...

February 23, 2019 · 2 min · jiezi

<译>米田嵌入

上一篇:米田引理这一篇正在翻译当中。。。

February 16, 2019 · 1 min · jiezi

<译>米田引理

上一篇:可表函子原文地址:https://bartoszmilewski.com/2…这一篇正在翻译当中。。。

February 13, 2019 · 1 min · jiezi

【Haskell】《魔力Haskell》Ch01~Ch10勘误总结

想了解下Haskell,于是乎就买了三本纸质书籍:《魔力Haskell》、《Haskell函数式程序设计》和《Haskell函数式编程入门》,结合网上免费的电子版《Learn You a Haskell》,相互映照来学习。现在的进度是看完了《Learn You a Haskell》,《魔力Haskell》看完了前10章,另外两本仅仅看了一些开头。通过学习实践发现,真应了《魔力Haskell》作者前言的话,现在Haskell资料过时比较严重,很多书里的代码用最新的GHC运行是有问题的。整体而言,《魔力Haskell》这本书内容还算是新的,但错误也不少。所以,现在把一些错误总结一下,方便同看这本书的人,也记录下自己啃书的经历。ps. 作者在github上有开的issues,自己还提交了一些错误,但错误没有排序,不利于查看。错误详单以下是一些不妥的地方:P8 倒数第8行,应该为反引号。改为:两侧加上即可。P14 倒数第8行,证明这个词用得不妥,有人已经指出来了。我觉得改为验证可能更合适。P21 倒数第4行,截至现在(GHC 8.6.3),标签冲突的问题还没解决。暂时还得按书上的建议来。P27Prelude的那些部分函数也都有对应的全函数版本。这块作者没说清楚,实际上这些安全版本在safe包内。P31列表实际上是一颗一叉树“颗”改为棵;应为二叉树,该页上面的图已经很明显了。P39 “4.3.1”标题上面的那句话,应为replicate这类返回函数的函数被称为高阶函数P43~P44 中的所有[100..1]都改为[100,99..1]。原因&lt;Learn You a Haskell&gt;里已经有解释了,只不过例子是[20..1]要得到从20到1之间的列表,[20..1]是不可以的,必须得[20,19..1]。对于没有提供步长的区间(如[20..1]),Haskell会先构造一个空的列表,随后从区间的下限开始,不停地增长,直到大于等于上限为止。既然20已经大于1了,那么所得的结果只能是个空列表。P45 第3行,(2^)改为(^2)P48 最底下的两段伪代码是有问题的,语法都有问题:第一个伪代码可改为:case ... of pat1 | guard1,guard2,... -&gt; ... pat2 | ... -&gt; ... . . .第二个伪代码可改为:case () of _ | ... -&gt; ... | ... -&gt; ...完整的语法可参考&lt;haskell2010 report&gt; 3.13 Case Expressions 一节。P49 ghci示例的代码去掉两个引号即可。参见&lt;haskell2010 report&gt; 2.7 LayoutP53 FTP这个缩写让人找不到北,是Foldable/Traversable in Prelude proposal的缩写。P55 foldr的展开,作者用了加法交换的定律对展开式进行了二次加工,实际上原始的展开式为:foldr (+) 0 [1,2,3]-- (+) 1 (foldr (+) 0 [2,3])-- (+) 1 ((+) 2 (foldr (+) 0 [3]))-- (+) 1 ((+) 2 ((+) 3 (foldr (+) 0 []))) -- (+) 1 ((+) 2 ((+) 3 0))-- 1 + (2 + (3 + 0))P57 第7、8行,将数组改为列表 P59 最后一段首行,边字有误,改为遍P60 maximum改为minimumP67 nub代码有误,改为nub [] = []nub (x:xs) = if x elem` xs then nub xs else x : nub xsP73 isBigger类型标注缺少::P76 import Data.Word 应该放在maxBound :: Word之前,否则是没意义的P81 构造函数首字母大写,应为MakeURLp82 由于Inch 4,图中y盒子里的数字应为4P86 qsort第二个模式匹配最右侧多了一个),去掉即可。看完Ch11~Ch20,再写这部分的问题。加油!! 请关注我的公众号哦。 ...

January 30, 2019 · 1 min · jiezi

<译>可表函子

上一篇:自由幺半群原文地址:https://bartoszmilewski.com/2…这一篇正在翻译当中…

January 21, 2019 · 1 min · jiezi

<译>自由幺半群

上一篇:极限与余极限原文地址:https://bartoszmilewski.com/2…这一篇还在翻译当中…

January 20, 2019 · 1 min · jiezi

极限与余极限

这一篇还正在翻译当中。。。

January 15, 2019 · 1 min · jiezi

<译>声明式编程

修改LaTeX调试中,还未翻译完成,如果你看到了这篇文章,请当天晚些时候再来看看吧第二部分的导言在本书的第一部分我曾说范畴论和编程都与可复合性相关。在编程时,你总会不断地把问题分解到一个你能处理其细节的程度,然后一个个地解决每个子问题,最后把它们自底向上地重新组合起来。这里,大致来说,有两种实现的方法:告诉计算机要做什么(what to do),或者告诉它如何去做(how to do it)。也就分别是声明式(编程)和命令式(编程)。你甚至能够从最底层的地方来理解这件事。复合本身可以被声明式地定义,例如h是f与g的一个复合:h = g . f或者命令式地定义,这时,先调用f,保留计算结果,再对该结果调用g:h x = let y = f x in g y一个程序的这种命令式版本通常被描述成按照时间顺序进行的一个指令序列。尤其是,对g的调用不能发生在f执行完成之前。至少这是一种概念上的想象————在一个参数传递方式为按需调用的惰性语言中,实际的执行顺序可能完全不同。实际上,声明式代码和命令式代码的执行过程只会有一点甚至没有差异,这取决于编译器有多聪明。但这两种方法论毕竟不同,尤其在我们寻找问题的解决方案时和考虑代码的可维护性和可调试性时,它们会不一样的彻彻底底。这里有一个重大问题:当面对一个具体问题时,我们是否总是可以在声明式和命令式的方法中选择一个?进一步,如果有一个声明式的的解决方案,是否一定可以转化为计算机代码?这个问题的答案远非显然,并且,如果我们能够找到这个问题的答案,我们对宇宙的理解可能就会迎来一场革命。让我来详细说说。这个问题在物理中有一个类似的对应,它要么会是一些潜在的深刻原则的一个重要部分,要么会告诉我们一些有关大脑如何工作的事。理查德·费曼曾经提到,这个对应启发了他在量子电动力学领域的工作。大部分的物理定律有两种表达形式。一种使用局部(local)的观念,或者说无穷小(infinitesimal)和分析(considerations)。我们会在一个小领域内观察系统的状态,并且预测它在下一时刻如何演变。这种观念通常用一组的微分方程组的形式表达,并且我们会在一个周期时间里对它们做积分或求和。让我们看看这种形式和命令式思维有多像:我们通过一系列的“小碎步”达到最终解,而每一个碎步取决于前一步的结果。事实上,物理系统的计算机仿真也是按部就班地把微分方程组重写为差分方程组,然后迭代它们。这也是行星游戏中宇宙飞船的运动方式。在每一个时间步长里,飞船的位置通过一个小的增量改变,它就是速度乘以时间间隔。而速度呢,也是如此迭代计算,它的小增量正比于加速度,也就是力除以质量。牛顿运动定律所对应的微分方程组有明确的写法:F = m dv/dtv = dx/dt同样的方法可以用来处理更复杂的问题,比如用麦克斯韦方程组来描述电磁场的传播,或者甚至是用格点量子色动力学来描述一个质子中的夸克和胶子的行为。这种局部思维方式与离散的时空有关,而史蒂芬·沃尔夫勒姆的将整个宇宙的复杂度约减为一个元胞自动机系统的伟大构想就是用数字计算机计算这些离散的时空。另一种是全局的方法。我们观察系统的初始状态和终止状态,然后通过最小化某个函数计算出两者之间的轨迹。最简单的例子就是费马的最小时间原理。它声称光线会沿着时间最小的路径传播。特别地,当没有反射物或折射物时,光线会沿着最短的路径传播,也就是一条直线。但是,光在厚实(透明)的材料中会传播的更慢,比如水或玻璃。所以如果你选择的起点在空气中,终点在水里,那么光就会在空气中传播更长一点以获得水中路线的缩减。这个最小时间所对应的路径使得光在空气和水的界面处发生折射,导出乐斯涅尔折射定律:$$ \sin \theta_1 / \sin \theta_2 = v_1 / v_2 $$其中,$$ v_1 $$是光在空气中的传播速度

January 10, 2019 · 1 min · jiezi

对现代C++的一点看法

背景逛水木社区C++版块,看到了一篇很有意思的帖子–《C++20会变得陌生》。楼主贴出了分别用C++11 和 C++20编写的代码,如下:void cpp_11() { std::vector<int> v{1, 2, 3, 4, 5}; std::vector<int> even; std::copy_if(v.begin(), v.end(), std::back_inserter(even), [](int i) { return i % 2 == 0; }); std::vector<int> results; std::transform(even.begin(), even.end(), std::back_inserter(even), [](int i) { return i * 2; }); for (int n : results) std::cout << n << ’ ‘;}void cpp_20() { std::vector<int> v{1, 2, 3, 4, 5}; v | ranges::view::filter([](int i) { return i % 2 == 0; }) | ranges::view::transform([](int i) { return i * 2; }) | ranges::view::foreach([](int i) { std::cout << i << ’ ‘; });}以上代码实现了数据操作:从向量v里筛选出偶数的元素将以上得到的每个元素分别乘以2将以上得到的每个元素分别打印出来C++11 通过使用algorithm里的几个函数按步骤实现;而C++20 通过使用新的ranges扩展实现了相同的效果,通过使用|将数据连接了起来,类似于Unix shell的管道。我猜作者的意思应该是想表达C++20变化大,会让一些人感到陌生,但下面的评论就很有意思了,大体有以下几类:1.“颜值派”:这语法可真丑2.“保守派”:C++在函数式编程上路越来越作死3.“逃离派”:建议使用Rust或Go替代越来越臃肿的C++4.“现实派”:C++真是越来越复杂了,连语法都看不懂,哪一天会撑爆5.“理性派”:C++的一些痛点必须解决,而目前看来也只能这样解决几点看法近几年,C++的演进进入了快车道,之前C++98到C++11,历时13年;现在C++11、C++14、C++17、C++20,每3年一版,带来了改进的同时,也引起了众多的吐槽,比如上面的评论。现在是我的一些看法,为了避免发散,针对上面的代码做讨论。关于语法美丑永远是相对的,得看你跟谁比。比如,如果简洁是一种美,那跟纯函数式语言Haskell比,肯定要丑了,下面是Haskell等价的代码,很明显要简洁的多。[1,2,3,4,5] & filter even & map (*2) & mapM_ print跟C语言比呢?如果抽象是一种美,我觉得C++要美观的多,比如C中只能通过循环来实现遍历,而现代C++中已经有了很多结构控制函数,比如foreach、filter等,这种函数望文生义,你很明白就能知道代码在干什么,而循环语句则需要你进到循环内部才能明白在做什么。我是从C转向C++的,起初写C++代码的时候,根本就不用引用、namespace这些东东,而是用指针和命名前缀代替,当时也觉得不美。久而久之发现,指针传参的时候要判断指针是否为空,这样的代码真的很丑,那么长的标识符也很丑,而引用和名字空间恰好解决了这些问题,反倒是觉得好看了。C++20 跟 C++11比呢?C++ 20美,因为上文的代码既简洁,又清晰了表达了代码意图。C++语法是有巨大的历史包袱的,想进步很难,但C++在语法上面是在进步的,比如使用{}统一初始化语句,虽然初看起来比较别扭,但一旦统一了,代码看起来是比较漂亮的。代码一致性也是美的一种。关于函数式函数式编程已经成了编程语言的必备特性,不光C++引入了,其他语言,比如Java等等都包含了某些函数式语言的特性。从某种程度上来说,函数式编程是趋势。比如,匿名函数在某些情况下是很方便的,避免每次都要去定义一些仅用一次的函数。C++20可能要引入的concepts也来源于函数式编程语言,包括Rust的trait,都跟haskell的typeclass有关,一旦编译器支持了,对模板编程绝对是一大利好。C++既然是多泛型语言,再多支持一个函数式,也就不奇怪了。关于C++的替代语言这方面,Rust和Go的呼声最高。在我看来,C++不会被替代,但Rust是一种比C++更好的选择,而Go可能是Java的替代。简要说下两门语言的对比:Rust和Go的语言设计是两个方向,前者从学术化出发,兼顾实用化,后者从实用化出发,慢慢引入其他高级特性。1.从整体上看,Rust语言要完整,后期出现坑的可能性要小,Go通过简单吸引了大量的开发者,在1.0版泛型都没有,而且异常处理极其单薄。从语言一致性上看,Rust较好。2.从语言效率看,Rust跟C++都没有GC,速度相当;Go有Gc,速度是优于Java的,但离C++还有一段距离,而且这个距离很难再减了。有一点要提,Go入门实在是太快了,稍微看一下语法,在ide里就可以写代码了;而Rust的所有权、borrow机制,尤其是生命期就拦住了不少人。如果没有特殊需求,优选新语言,因为新语言历史包袱少,能剪掉很多学习成本。关于C++的体量这几年,C++加快了演进速度,越来越多的特性加入到了语言当中,相对地,C++做减法的速度却没快起来。这导致了一种结果,就是C++语法膨胀的太快了。而且C++还要兼容C语言,最后语言规范刷刷的涨。关于对策,语言的标准我们是没有可操纵空间的,但我们可以选择自己的“个人标准”。如,你是做应用开发的,用到底层特性的可能性不大,这个时候,你就完全可以不用原始指针、不用宏、仅使用{}初始化。。。这样是完全可以的。总结现代C++是在进步的。后续文章开始写一些现代C++的语言或库的特性了,大部分都是老工具很好的替代。请关注我的公众号哦。 ...

January 9, 2019 · 1 min · jiezi