niszetの日記

アナログCMOS系雑用エンジニアが頑張る備忘録系日記

Haskellを書き始める(勉強進捗メモ)

初心者なので…。

昨日ふと思い立って書き始めた。やはり読むのと書くのでは全然違う。知ってることと出来ることは違うように。

この記事はどこで躓いていたのかを記すだけのメモ的な日記です。あとで振り返るため。なので、有益なことは書かれていません。

また、半月に1度(月2回)を目途に進捗を書いていきたい。とはいえ、普段投稿する記事とどう分けるのかは何も決まっていません。まぁやってれば固まるさ…

GHCiで複数行入力

関数を定義する場合などは複数行一気に入力をしないといけないので、:{ ... :} が必要。最後が:}である点に注意。何度も書くようであればファイルに書いた方が良い。

deriving Show

これをつけないと、GHCi上で表示しようとしたときに怒られる。

識別子

dataの左辺と右辺で型とか値とかコンストラクタとか色々呼び名があってまだ整理できていない。

今後、このあたりを参照、調査。

https://www.slideshare.net/knzm/haskell-7-14864975

たとえば以下のような記述はNG.

`data Atom = Atom String | Atom Char deriving Show

次のようなエラーが出る。

Multiple declarations of ‘Atom’

Stringを受け取るやつとCharを受け取るやつで名前を変えておく必要がある。そういえば本にも書いてあったな…。

次の書き方はOK.

data Mole = Mole [(Int, Atom)] deriving Show

右辺のMoleを消すとダメ。

    Cannot parse data constructor in a data/newtype declaration: [(Int,
                                                                   Atom)]

isUpperを使いたいならimport Data.Charを入れる

Preludeに入っているのは本当に基本的な関数だけ。import Data.CharするまえにiSUpperを使おうとすると怒られる。

<interactive>:6:1: error: Variable not in scope: isUpper

引数なしだと当然エラー

<interactive>:8:1: error:
    ? No instance for (Show (Char -> Bool))
        arising from a use of ‘print’
        (maybe you haven't applied a function to enough arguments?)
    ? In a stmt of an interactive GHCi command: print it

isUpperはCharに対する関数なので、Stringを引数に与えても当然エラー。

Prelude Data.Char> isUpper "a"

<interactive>:9:9: error:
    ? Couldn't match expected type ‘Char’ with actual type ‘[Char]’
    ? In the first argument of ‘isUpper’, namely ‘"a"’
      In the expression: isUpper "a"
      In an equation for ‘it’: it = isUpper "a"

優先順位づけに()をたくさんつける。

先の Atom String を受け取る関数を定義するとき、(Atom String) のように括弧で囲わないと引数の数があわないぞ~みたいなことを言われる。型の定義でAtom a => a -> Bool 等はしない。そもそも違う?要チェック。

タプルの()と優先順位の()がわかりづらいなと思っていたが、前者は,が必要。後者は不要なので見ればわかることだった。

本当はもっと厳しいチェックにする(既存のものに含まれるかをチェックする)が、今回は書き方を学びたいので、1文字でかつ大文字であること。あるいは2文字で1文字目が大文字で2文字目が小文字であることをチェックし、そのいずれかであればTrue、それ以外はFalseという関数にしてみた。とりあえずこんな感じ。

isAtom :: Atom -> Bool
isAtom (Atom x@(a:b:_)) 
  | (length x == 2) && (isUpper a) && (isLower b) = True
  | otherwise = False
isAtom (Atom x@(a:_)) 
  | (length x == 1) && (isUpper a)                = True
isAtom _ = False

as記法x@(a:b:_)を使うのは、文字数と内部の文字それぞれをチェックしたいため。&&か&かで迷ったが、:t (&&)みたいにコンソールで確認すればよい(なければ怒られるので)。:tは:type。(&&)は演算子&&の関数での書き方。

いたるところに()があるが、基本的に左結合で持って行かれてしまうので。

otherwiseは万能ではない…

いや、最初のマッチで拾えれば、確かに常にTrueなのだが、上の例で、x@(a:b:)は少なくとも2文字ある状態でマッチし、1文字の場合はマッチしない。この場合、その式?はスキップされるので、otherwiseは実行されない。そのため、1文字の場合は別途定義をしてあげる必要があるし、そもそもコレだとは不要な気がする。通ったのでそのままだが、また調べよう。

これ以外のケースを拾う場合は最後のようにisAtom _で拾う。これでisAtom (Atom "a")はFalseを返す。こうなると、最初のotherwiseは不要そうだな…。あとでまた書き換えよう。

関数に直接与える場合は()つけないと2引数と思われてしまうが、先に変数に代入しておけば()は不要。

x = Atom "He"
isAtom x
> True

実際に存在する元素名であるか?をチェックする場合はあらかじめそれらをリストに入れておいて、その中にマッチするものがあるか?のチェックが必要。その前チェックに↑のチェックを入れても良いが。

一応それっぽい動きをしている。

isAtom (Atom "あ")
> False
isAtom (Atom "2")
> False
isAtom (Atom "H2O")
> False
isAtom (Atom "HO")
> False
isAtom (Atom "H")
> True
isAtom (Atom "O2")
> False
isAtom (Atom "Al")
> True
isAtom (Atom "Ale")
> False

まとめ

やはり、コードは書いてなんぼ…。多分読むだけより数倍~数十倍の効果がある。しかし、前知識がないと無駄に時間を溶かすのも事実なので、バランスが難しいなと。

しばらくはHaskellで色々遊んでみたいので、ゆるい記事が続きますが、ここは私の備忘録なので…。

とりあえず簡単なパーサーを書こうと思います。