個人檔案Nothing :: Maybe Future部落格清單訪客留言更多 工具 說明

部落格


20 January

Haskell 的 IDE

    Haskell 作为 FP 的典型语言,目前尚无成型的 IDE 环境,为了方便学习,于是考虑自己整合一个。因为对 UltraEdit 比较熟悉,于是考虑利用它来扩展一下。
    要把 UE32 作为 Haskell 的 IDE 使用,要做几个方面的工作。
    第一个是语法高亮。需要修改 UE32 的 wordfile.txt 文件。内容较多,由于现在学习得还没入门,整理得还不够完善,暂不发出来了。以后补上。
    第二个是编译和调试。可以利用 UE32 的自定义命令来调用 GHC 进行编译。具体操作如下:
《在 UE32 加入 GHC 的编译命令方法》
  假定 GHC 的安装目录是“D:\GHC”,UE 的安装目录是“D:\UE32”,注意根据实际情况修改下面的命令。
  在菜单中选择 [高级] -> [工具栏配置] 。
  添加项目:
    项目名称“GHC编译”;
    命令行“( ( (echo.[ Start compiling ]) & (D:\GHC\bin\ghc.exe --make %f -o %n.exe) ) && (echo.[ OK ]) ) || (echo.[ Failed ])”;
    工作目录“%p”;
    选项页:
      选择“DOS程序”;
      选中“保存活动文件”;
    输出页:
      选择“输出到列表框”;
      选中“捕获输出”;
      选择“不替换”;
    所有未提到的复选框均不必选中。
  点击“应用”即可保存。
  同样方式添加这样两个项目:
    项目名称“GHC编译运行”;
    命令行“( ( (echo.[ Start compiling ]) & (D:\GHC\bin\ghc.exe --make %f -o %n.exe) ) && ( (echo.[ Start runing ]) & ( %n.exe ) & (echo.[ Finish runing ]) & ( pause > NUL ) ) ) || (echo.[ Failed ])”;
    其它设置同“GHC编译”。
    项目名称“GHC分析”;
    命令行“( ( (echo.[ Start compiling ]) & (D:\GHC\bin\ghc.exe -prof -auto-all --make %f -o %n.exe) ) && ( (echo.[ Start runing ]) & ( %n.exe +RTS -p -RTS ) & (echo.[ Finish runing ]) ) && ( (echo.[ Show profile info ]) & (D:\UE32\Uedit32.exe %n.exe.prof) ) && (echo.[ O K ]) ) || (echo.[ Failed ])”;
    其它设置同“GHC编译”。
  确定以后就可以在 [高级] 菜单的最下方看见这三个项目及其快捷键,UE32 v12 里是 CTRL+SHIFT+0 到 CTRL+SHIFT+2 。
  这样,就可以在用 UE 编辑 Haskell 代码的时候随时进行编译或测试了。
  要注意的是,要修改命令行以适应本地的安装配置。
17 October

学习 Haskell

想开始认识一下这个据说是最贴近数学语言的计算机语言。
 
从 haskell.org 上选了 GHC 作为编译器,利用已有的 UltraEdit-32 作为编辑器,Yet Another Haskell Tutorial 作为学习的参考资料,再加上 Refference, CardGHC 的文档,可以开始了。
 
持续更新中……

####
## 1月23日
- 普通的数据实体类型 (type) 用 * 表示,种类 (kind) 的类型是 (* -> *) ,如 Maybe 和 List 。
- 定义的类型别名 (type synonym) 可以用来定义其他的类型别名,限制是在定义时必须以完整参数表的形式给出定义式。
- 利用 IO 类型是 Monad 实例的特性,文件操作可以简单地使用类似这样的表达式:
    readFile "testdata.txt" >>= \s -> putStrLn (show s)
  * 一个附加问题:Haskell 如何操作二进制文件?
    1.  writeFile
      测试这样的表达式:
        writeFile "data.bfile" $ map (\a -> toEnum a::Char) [0..255]
      其输出文件中,数字 10 被转换为十六进制 0D 0A ,多了一个 0D 。也许是“文本格式”的原因。
    2.  readFile
      测试这样的表达式:
        readFile "data.bfile" >>= putStrLn . show . map fromEnum
      根据输出,0D 0A 被还原为 10 暂且略过。文件读到 1A 就认为是结束了。
      经测试,IO 提供的其他判断文件结束的函数也是同样效果。
- Monad 的几个基本方法:
    class Monad m where
        return  :: a -> m a
        fail    :: String -> m a
        (>>=)   :: m a -> (a -> m b) -> m b
        (>>)    :: m a -> m b -> m b
- IO 实现 do 符号的四个规则 (亦即编译器处理 do 子句的方式) :
    1.  do {e} →
          e
    2.  do {e; es} →
          e >> do {es}
    3.  do {let decls; es} →
          let decls in do {es}
    4.  do {p <- e; es} →
          let ok p = do {es}
              ok _ = fail "..."
          in  e >>= ok
  这四个规则联合起来实现了 do 子句调用 Monad 的过程。
  换言之,可以直接用 Monad 本身的语法写出无 do 子句的 IO 函数来。(虽然这不够直白)
- Monad 必须满足的三条法则 (若自定义不满足的,就可能有一些内建运算无法使用?也许可以利用这个特性测试编译器?) :
    1. return a >>= f ≡ f a
    2. f >>= return ≡ f
      <两种展开,乘一律>
    3. f >>= (\x -> g x >>= h) ≡ (f >>= g) >>= h
      <结合律>
      eg. getLine >>= (return . reverse) >>= putStrLn
- §9.3 的例子总结。
  ㈠  证明了一个 Monad 的基础函数符合三条法则。
  ㈡  一个典型的 Monad 构造过程:
      1. 写出一个常规函数。
      2. 比较常规函数和原形函数的差别,把差别抽象成类型。
      3. 定义(实现)抽象类型的“接口”(return 和 bind)。
      4. 将新类型代入常规函数,写出常规函数的 Monad 版本。
      在这个过程中,定义的 Monad 类型不是符合原形中的数据结构类型,而是,对其进行扩展的数据单元类型。
  * 应能对此进行扩展,目前没想到。
-

####
## 1月19日
- §8.4.2 里介绍了一个 Computation 类。此类是 Monad 的一个近似型。而对 OO 语言来讲,它是一个计算接口类。提供了四个接口:success 、failure 、augment 和 combine 。
  这四种方法把某种计算的结果封装到一起,其中包含了可能的成功和失败。这该是 Monad 的主要思想。
  从 Computation 类的定义看:
    class Computation c where
        success :: a -> c a
        failure :: String -> c a
        augment :: c a -> (a -> c b) -> c b
        combine :: c a -> c a -> c a
  success 把一个数据类型 a 封装成匹配 Computation 类的类型 c a ,表示一个成功的计算;
  failure 接受一个字符串,返回一个 Computation 类的量,表达失败的计算;
  augment 接受一个计算,一个将这个计算变为另一个计算的变换,从而产生新的计算。
  combine 接受两个之前的计算,产生一个新的,作为两者的结合。这个运算不是 Monad 必须的接口,但在 MonadPlus 中可以找到。
  在 Monad 类中,success 叫做 return ,failure 叫做 fail 以及 augment 叫做 >>= (bind) 。
  * 上面基本属于直译,因为觉得还是这样说起来稍微好接受点。
-
 
####
## 1月18日
- 两种类型定义:data①、newtype② 和 type③ 。
  ①定义的类型的数据集中含“bottom”;②用来定义自己实现的 Int 类型。
  ①的定义方式没有特别的局限;②的定义格式是受限制的 MyType a 方式。
  ③定义的是一个明确结构的别名。
  ⑴三者都可以实现某类的 instance 。
  ⑵①和③可以 deriving 某类。
-
 
####
## 12月18日
- 对数列的处理。
  两个函数(家族):zip、unzip
  截取函数:take、drop
  整合参数:curry、uncurry
  数列的另一个定义方式:[ f x | x <- xSet, otherConditions ]
- 库中常用数列类型复杂度。
          List    Array   FiniteMap
  insert  O(1)+   O(n)    O(log n)
  update  O(n)    O(n)    O(log n)
  delete  O(n)    O(n)    O(log n)
  find    O(n)    O(1)+   O(log n)
  map     O(n)    O(n)    O(n log n)x
- 可以用 foldx 结合函数运算(“.”等),简化很多 list 的处理表达式。
-

####
## 12月10日
- 实例定义。
    instance BaseClass MyDataTypeOrClass where
        OneCompleteDefinationMethods ...
    instance BaseClass x => BaseClass (MyDataTypeOrClass x) where
        OneCompleteDefinationMethods ...
  一些常用类的最小完备方法集:
    Eq :  A (==) :: Eq a => a -> a -> Bool
          B (/=) :: Eq a => a -> a -> Bool
    Show :  A show :: Show a => a -> String
            B showsPrec :: Show a => Int -> a -> String -> String
              showList :: Show a => [a] -> String -> String
    Read :  A readsPrec :: Read a => Int -> String -> [(a, String)]
              readList :: String -> [([a], String)]
    Ord : A compare :: Ord a => a -> a -> Ordering
          B (<=) :: Ord a => a -> a -> Bool
          C (>) :: Ord a => a -> a -> Bool
          D (>=) :: Ord a => a -> a -> Bool
          E (<) :: Ord a => a -> a -> Bool
          F min :: Ord a => a -> a -> a
          G max :: Ord a => a -> a -> a
    Enum :    pred :: Enum a => a -> a
              succ :: Enum a => a -> a
            A toEnum :: Enum a => Int -> a
            A fromEnum :: Enum a => a -> Int
              enumFrom :: Enum a => a -> [a]
              enumFromThen :: Enum a => a -> a -> [a]
              enumFromTo :: Enum a => a -> a -> [a]
              enumFromThenTo :: Enum a => a -> a -> a -> [a]
    Num : A (-) :: Num a => a -> a -> a
          A (*) :: Num a => a -> a -> a
          A (+) :: Num a => a -> a -> a
          A negate :: Num a => a -> a
          A signum :: Num a => a -> a
          A abs :: Num a => a -> a
          A fromInteger :: Num a => Integer -> a
-

####
## 12月9日
- 文件操作,多半需要 bracket 作为 wapper 来使用。
- 模块定义:
    module MyModule
    module MyModule ( NoInstanceDataType(),
                      PartValidDataType( PublicDataValue ),
                      FullValidDataTypeA( FullValidDataTypeA ),
                      FullValidDataTypeB( .. ),
                      PublicFunction                            )
  模块引入:
    import MyModule hiding ( PrivateDefinations )
    import qualified MyModule ...              -> MyModule.MyFunction ...
    import qualified MyModule as MyShortName   -> MyShortName.MyFunction ...
  多级模块(非 Haskell 98 标准,GHC 要 -i 参数):
    import qualified SubDir.MyModule as MyShortName
- 代码的书写,可读性编程(Literate Programming),.lhs 代码
  Bird-script(有点仿 BBS 或 Email 的回复引文的味道)
    1. 代码行首加“>”字符,其他不加。
    2. 代码行与注释行间有空行分隔。
  LATEX-style markup
    用 \begin{code} 行和 \end{code} 行明确代码行的范围。
- 函数式编程另类指南 http://chn.blogbeta.com/232.html
- where 与 let 。同一, where 子句是转换为 let 子句后进行编译的。
- () 转换运算符为函数。可以映射为省略参数的内嵌函数。
  `` 转换二元的函数为运算符。
- 多参函数可以理解为多阶函数。接受一个参数后,输出一个函数,接收下一参数,类推。
  这种函数可以赋值,可以进行一些函数运算。
- 与 . 运算组合函数类似,$ 运算应用函数。但 $ 运算优先级比较低,可以用以在一些情况下替代括号。
- 通过基于函数的运算,对定义进行纯数学推导。可以优化与分析函数。
- 几个调整参数的函数。
    curry   - 将第二、三个参数做成 pair 给第一个参函数作为参数。取返回值。
    uncurry - 将第二个 pair 参数分解为两个值给第一个参函数作为参数。取返回值。
    flip    - 将第二、三个参数顺序倒置,给第一个参函数作为参数。取返回值。
-

####
## 11月13日
- CPS, 这种机制的实现,除了函数本身工作所需要的参数之外,需要接受一个“继续”函数作为参数,用来决定完成以后如何继续工作。
  * 编译器实现上,用尾递归替代了函数直接返回?是否有额外的系统负担?
-

####
## 11月4日
- Just, Nothing, Nil 与 Cons, Left, Right 等,可以理解为定义类型的时候指定的特别的命名常量或命名运算。
  相应地,data 标识符可以定义一些常量。
- 对于 Either 这种数据类型,需要更详细的例子帮助理解。
-
 
####
## 10月21日
这两天忙于其他事情,没继续学习。
今晚 goo 了一下,发现一个数学上的强人用玩笑的语气写的一点关于 haskell 的东西,很精炼,似乎覆盖到了查 Reference 时所见到的绝大多数语法。其中还有几个应用的例子。最重要的是,用中文写的,所以存下来,这是链接
还有一个,这是链接
 
####
## 10月18日
- 函数定义直接使用等号。参数可以是任何类型。
- 由于优先级问题,参数不能写 -x ,必须加括号,封成一维向量。
- 函数中可以使用分支语句。
  if conditional
    then truePart
    else falsePart
  - 三个关键字都为必要关键字
  case exp of
    Pattern1  -> action1
    Pattern2  -> action2
    _         -> else_action
  还有参数匹配。(Reference card)
  Function pattern matching
   f [ ] = 0
   f [x] = 1
   f _   = -1
  Function conditionals (guards) (没懂这里的guards)
   f x | x == []        = 1
       | length x == 12 = 15
       | otherwise      = -1
- 函数定义开头可以使用 let var in expr 定义中间变量,也可以定义多个中间变量,多个中间变量必须有相同的缩进。(排版影响解析)
- 对于二元运算,加圆括号能使运算符(如加号)作为函数名来使用,反之,加反撇号(`)能使函数名(如 map )作为运算符来使用。
- 注释。“--”表示行注释;“{-”和“-}”含着跨行的注释。跨行注释可嵌套。
+ 缩进是语法的一部分,分多行的代码要注意。
- “<-”符号:name <- function ,运行 function ,结果命名为 name 。
- 用 const_value::Type 来明确某常量的类型。
- Haskell 函数可以分为“函数”和“过程”,函数的类型与其返回值类型相同,过程的类型不同于其返回值类型,所以,在过程中,不可以直接将过程名作为其值使用。
-
 
####
## 10月17日
- 大小写敏感。
- =号表示概念的定义,用时才展开。(“lazy”)
* 类型名以大写字母开头,函数或变量名以小写字母开头。
* 因为是 functional language ,所以对于一个定义明确的函数,haskell 消除了输出反过来影响该函数进程的副作用。
- 直接以向量的形式表达定元一维数组,各维度的值可以是各种类型(允许欠套)。特别地,二维向量(pair)可以使用 fst 和 snd 函数。
  也可以说,定元的数组用圆括号表示。
- 传统的 list 用以容纳变元的数组,haskell 用方括号表示,叫变元数组(暂称 list )。可以定义/使用空 list 。list 的各元素类型必须相同(允许欠套)。
  冒号用以向 list 添加元素,[] 记法必须在最后,前面可以有任意个元素加入。使用 length 、head 和 tail 函数进行操作。
  双加号用以连接两个 list 。
- 字符串就是元素为字符的变元数组,可以直接当作变元数组使用。
+ 定元与变元数组可以互相嵌套。
  函数运算优先级高于冒号运算和数学运算。(除括号运算外最高?)
  三维或更高维度的向量没法操作?(不能用 fst 函数对,也不能用 length 函数组。)
- 简单列表函数:
  映射:map 映射函数 变元数组
  过滤:filter 过滤条件函数 变元数组
  ××:foldr 某种二元运算符 值 变元数组
  ××:foldl ... ... ...
       运算符要加括号,优先级所限。(此函数对可能有十分重要的用处,又或者衍生出特别的用法。)
       * [修] 加括号是为了把运算符封装成一元向量,使其可以作为函数参数。(可以用多元向量否?)
         * [修] 加括号是为了把运算符封装成函数,使其可以作为函数参数。
       如果 foldl 用在无穷元数组,会死锁;而 foldr 可能可以产生返回值。(需要看后面才知道)
-