Haskellやる
基本的にこれをみてたけどわからん文法がある程度あって読めないので調べる
ghciで基本的な文法をさらう
:tで型の確認ができる
文字列は[Char]型(Stringの糖衣構文)
headは配列から先頭の値を取得する関数
GHCi, version 8.10.7: https://www.haskell.org/ghc/ :? for help
Prelude> name = "tosa"
Prelude> :t name
name :: [Char]
Prelude> :t head name
head name :: Char
Prelude> head name
't'
雰囲気で$を使うとコンパイル通るからつけてるけどこれはなに?
Prelude> print $ head name
't'
なるほど
括弧の代わりに $ を用いることもできます。$ は $ から行末までを (...) で囲むのと同じ意味になります。
do構文
module Main where
import Lib
main = do
putStrLn "hello"
putStrLn "world"
output
~/pra stack run pra-exec 3524ms 木 5/ 5 22:20:37 2022
hello
world
これでも大丈夫
main = putStrLn "hello" >> putStrLn "world"
Either型
Prelude> :t either
either :: (a -> c) -> (b -> c) -> Either a b -> c
Either型の実装
head' :: [a] -> Either String a
head' [] = Left "emptylist"
head' (x:xs) = Right x
main = do
case head' [1, 2, 3] of
Left s -> putStrLn $ show s
Right s -> putStrLn $ show s
case head' "helloworld" of
Left s -> putStrLn $ show s
Right s -> putStrLn $ show s
case head' [] of
Left s -> putStrLn $ show s
Right s -> putStrLn $ s
参考
Eitherの実装すくないな
LeftのException、Rightに結果
Eitherの実装にsemigroupがある!半群のことか!!
-- | @since 4.9.0.0
instance Semigroup (Either a b) where
Left _ <> b = b
a <> _ = a
instanceは型クラスの実装や!!!
class Semigroup的なのがあってそれを(Either a b)に実装しているんやな
class Semigroup 的なのがあって
あんまりオブジェクト指向のクラスと混ぜて考えないほうがいいと思われ. 僕は数学の本にしばしばみかける「××を満たすものを○○と呼ぶ」的なメンタルモデルで考えてます.
(自分の知識がなくて)何言ってるのかわからん...
このように演算子が一個だけ欲しいと思ったら、それは多分 Monoid だ。
monoidかmonadに見えた(?)、monadもmonoidだけど...
本文中にあるこれがすごく良き
> Metrics 1 2 <> Metrics 3 4
Metrics {rx = 4, ts = 6}
代数学での包含関係は半群->モノイド->群なので
GHC 8.4ではSemigroupがMonoidのスーパークラスとなり、Metricsに対する(<>)の定義がないために、エラーが出たという訳だ。
SemigroupをMonoidのスーパークラスにしたいよね的なモチベーションがあったらしい。
Functorを見ていく_
これなんだろ(?)
Left(Exception)はそのまま返す
Rightは関数を適用させてRightにくるんで返却
instance Functor (Either a) where
fmap _ (Left x) = Left x
fmap f (Right y) = Right (f y)
関手
あれ、ListだとputStrLnできないな
[Char]だとできるけど[Int]だとできない
isEmptyをEither返却にする。functorがあるからfmapが使えるか試す
isEmpty' :: [a] -> Either String [a]
isEmpty' [] = Left "empty"
isEmpty' x = Right x
main :: IO ()
main = do
case isEmpty' "helloworld" of
Left s -> putStrLn s
Right s -> putStrLn s
ちゃんと補完出してくれてえらい
あんまり綺麗なコードじゃないけどLeftの場合とRightの場合にfunctorを使用してみた
isEmpty' :: [a] -> Either String [a]
isEmpty' [] = Left "empty"
isEmpty' x = Right x
main :: IO ()
main = do
case (++"[END_SENTENCE]") <$> isEmpty' "helloworld" of
Left s -> putStrLn s
Right s -> putStrLn s
case (++"[END_SENTENCE]") <$> isEmpty' [] of
Left s -> putStrLn s
Right s -> putStrLn s
~/pra stack run pra-exec 2104ms 木 5/ 5 23:13:25 2022
helloworld[END_SENTENCE]
empty
<> <-これなに?
読めなすぎる
instance Semigroup (Either a b) where
Left _ <> b = b
a <> _ = a
さっき自分で描いてた
<> 結合律をもつ何らかの演算と考えておけばいいかな
ちゃんと満たしていそうで良き
Prelude> ([1] <> [2] ) <> [3]
[1,2,3]
Prelude> [1] <> ( [2] <> [3] )
[1,2,3]
Eitherの場合ですごい雑コード描いちゃったけどこれは何の演算になるんだろう
Prelude> a = Right 2
Prelude> b = Right 12
Prelude> c = Left 12
Prelude> a <> ( b <> c)
Right 2
Prelude> a <> (b <> c)
Right 2
Prelude> (a <> b) <> c
Right 2
したの実装の謎はとけたな。 a <> _ = aだから各コードの左側の値が返却されていて結合律をきちんと満たしている
instance Semigroup (Either a b) where
Left _ <> b = b
a <> _ = a
わかった、左側がLeft _ の場合は右側の値を返却している感じだ。
謎が解けた
Prelude> c <> c
Left 12
Prelude> c <> a
Right 2
Prelude> c <> b <> a
Right 12
当たり前だけど記号類(<|>, <*>, <$>)も関数として定義されていそう。
-- >>> mkState :: Applicative f => f MyState
-- >>> mkState = MyState <$> produceFoo <*> produceBar <*> produceBaz
(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 id
一番上のqiitaの構文解析の記事に戻って計算機をつくる。import Text.Parsec
と呼ばれるライブラリを使用している
expr :: ParsecT String u Data.Functor.Identity.Identity Int
expr = do
x <- number
fs <- many $ do
char '+'
y <- number
return (+ y)
<|> do
char '-'
subtract <$> number
<|> do
char '*'
y <- number
return (* y)
<|> do
char '/'
y <- number
return (`div` y)
return $ foldl (\x f -> f x) x fs
foldlの説明はこの記事がわかりやすかった
f [] = v
f (x:xs) = x * f xs
<*
, *>
の記法についての説明がされている。letter <* digit, letter
であるならばletter(文字のみが残るので出力はac
。letter *> digit, letter
であるならばdigitとletterの並びが残るので1cになるのかな
import Text.Parsec
import Control.Applicative ((<*), (*>))
main = do
parseTest (sequence [letter, digit, letter]) "a1c"
parseTest (sequence [letter <* digit, letter]) "a1c"
parseTest (sequence [letter *> digit, letter]) "a1c"
parseTest (sequence [letter >> digit, letter]) "a1c"
これでかけるのすごいけど理解が及んでない...
eval :: (Applicative f, Foldable t) => f b -> f (t (b -> b)) -> f b
eval m fs = foldl (\x f -> f x) <$> m <*> fs
apply f m = flip f <$> m
expr = eval term $ many $
char '+' *> apply (+) term
<|> char '-' *> apply (-) term
term = eval number $ many $
char '*' *> apply (*) number
<|> char '/' *> apply div number
number :: ParsecT String u Data.Functor.Identity.Identity Int
number = read <$> many1 digit
lintかかってないことに気がついたのでhlintを導入していきたい
liftA2が何だかわからないので調査。以下の記事にあたった
面白そうな記事、Real World HaskellにはHaskellでJSONパーサを書く例が載っているらしい。