Help us understand the problem. What is going on with this article?

PureScript by Example 3 章の後半を読む

More than 3 years have passed since last update.

この記事は (bouzuya) PureScript Advent Calendar 2016 の 8 日目です。 (bouzuya) PureScript Advent Calendar 2016 は bouzuya の PureScript 学習の記録です。

概要

昨日に続き PureScript by ExamplePureScript Language Guide を見ながら進めていきます。今日は PureScript by Example の 3 章 の後半を読みます。

※注意事項: 英語と日本語訳では大きな差異があります。0.10.x に対応している英語の側で進めます。

PureScript by Example 3.6

type キーワードで型を定義していきます。type は型シノニム (type synonyms) を定義します。

Language Guide:

type Entry =
  { firstName :: String
  , lastName  :: String
  , address   :: Address
  }
type Address =
  { street :: String
  , city   :: String
  , state  :: String
  }
type AddressBook = List Entry

ここでは firstName & lastName & address からなるレコードに Entry という名前の型シノニムを定義しています。レコードがレコードを含んでいます。

Language Guide の例を見ると type Bar a = { foo :: a, bar :: a } のような定義もできるようですね。へえ。

List EntryArray Entry は別物ですって。そりゃあ、そうでしょうね。

PureScript by Example 3.7

List は型コンストラクタ (type constructor) の例だそうです。名前のとおり型を構築するんでしょう。上記の AddressBook で使った List EntryList aEntry に適用されたもの。

種 (kind) の話ですね。

$ npm run pulp:psci

> chapter3@1.0.0 pulp:psci /Users/bouzuya/.ghq/github.com/paf31/purescript-book/exercises/chapter3
> pulp psci

impPSCi, version 0.10.2
Type :? for help

> import Data.List
> Nil :: List
Error found:
in module $PSCI

  In a type-annotated expression x :: t, the type t must have kind *.
  The error arises from the type

    List

  having the kind

    * -> *

  instead.

in value declaration it

See https://github.com/purescript/purescript/wiki/Error-Code-ExpectedType for more information,
or to contribute content related to this error.

> :kind Int
*
> :kind List
* -> *
> :kind List Int
*

関数適用みたいですね。List は型引数をひとつとって型を返すわけですね。

Language Guide:

PureScript ではほかにも !# があるんですね。また後で出てくるみたいです。

PureScript by Example 3.8

showEntry :: Entry -> String
showEntry entry = entry.lastName <> ", " <>
                  entry.firstName <> ": " <>
                  showAddress entry.address

型を書いてから、関数を書いていますね。実装は (<>)String をくっつけているだけですね。

showAddress も大差ないですね。スルー。

PureScript by Example 3.9

pscishowAddressshowEntry をためしていますね。スルー。

$ npm run pulp:psci

> chapter3@1.0.0 pulp:psci /Users/bouzuya/.ghq/github.com/paf31/purescript-book/exercises/chapter3
> pulp psci

PSCi, version 0.10.2
Type :? for help

> import Data.AddressBook
> let address = { street: "123 Fake St.", city: "Faketown", state: "CA" }
> showAddress address
"123 Fake St., Faketown, CA"

> let entry = { firstName: "John", lastName: "Smith", address: address }
> showEntry entry
"Smith, John: 123 Fake St., Faketown, CA"

PureScript by Example 3.10

emptyBook :: AddressBook
emptyBook = empty

当然ですけど emptyBook は空の List にするみたいです。

insertEntry :: Entry -> AddressBook -> AddressBook
insertEntry entry book = Cons entry book

psciCons の型を確認して、想定した型と一致することを確認していますね。

> import Data.List
> :type Cons
forall t1. t1 -> List t1 -> List t1

AddressBookList Entry なので t1Entry だと解釈すれば一致します。

PureScript by Example 3.11

何度かスルーしたカリー化ですね。PureScript の関数はカリー化された形であり、ひとつの引数しか取れません。複数の引数のようなものが必要な場合には、次の引数をとる関数を返します。

たとえば insertEntryEntry -> AddressBook -> AddressBook なので、Entry をとって AddressBook -> AddressBook という関数を返します。返されたこの関数はさらに AddressBook をとって AddressBook を返します。型のとおりですし、関数の引数は常にひとつです。この例のように、関数の一部の引数だけを渡すことを部分適用と言います。

> let john = { firstName: "John", lastName: "Smith", address: address }
> let insertJohn = insertEntry john

insertJohn は部分適用した結果、得られた関数です。john を insert する関数です。 (そんなに john ばかり要らない気はしますが……)

PureScript by Example では加えてイータ変換に触れています。

insertEntry :: Entry -> PhoneBook -> PhoneBook
insertEntry entry book = Cons entry book

これは

insertEntry :: Entry -> PhoneBook -> PhoneBook
insertEntry = Cons

と同じ動きをします。このように同じ引数なので、わざわざ指定しなくても良いのです。

この形式には利点があるのでしょうが、個人的には引数を明示しているほうが読みやすいように感じます。型を見ないと何を取るのかもよく分かりませんし……。まだ慣れていないだけかもしれません。

PureScript by Example 3.12

List のための関数を組み合わせて AddressBook から Entry を探す関数を定義するようです。

> import Data.List
> :t filter
forall a. (a -> Boolean) -> List a -> List a

> :t head
forall a. List a -> Maybe a

filter はカリー化された 2 引数の関数ですね。第 1 引数の関数で第 2 引数の List を filter して、その結果である List を返すみたいですね。

headList の先頭要素を返します。

Pursuit:

a をそれぞれ AddressBook における要素に置き換えると次のようになります。

filter :: (Entry -> Boolean) -> AddressBook -> AddressBook
head :: AddressBook -> Maybe Entry

これらを使って findEntry をつくります。

findEntry :: String -> String -> AddressBook -> Maybe Entry
findEntry firstName lastName book = head $ filter filterEntry book
  where
    filterEntry :: Entry -> Boolean
    filterEntry entry = entry.firstName == firstName && entry.lastName == lastName

($)where が初出なんですけど、概要としては filter したものを head しているだけです。

where で補助関数として filterEntry を定義しています。findEntry の引数に渡された firstNamelastName に一致する Entry かを Boolean で返す関数ですね。

PureScript by Example 3.13

中置演算子 (infix operator) ・中置関数 (infix function) の話です。上記の ($) に関連した話です。

Pursuit:

head $ filter filterEntry bookhead (filter filterEntry book) と同じ動きをします。

($)apply の alias で次のように定義されています。

apply :: forall a b. (a -> b) -> a -> b
apply f x = f x

infixr 0 apply as $

($) は右結合の低い優先順位の演算子として定義されています。 apply は第一引数の関数を第二引数の値に適用する関数です。

($) を使えば括弧の入れ子を解消できます。

street (address (boss employee))
street $ address $ boss employee

なるほど、便利ですね。

ちなみに関数を演算子的に使うことも、その逆もできます。演算子を関数的に使う場合は () で括り、関数を演算子的に使う場合は ` で括れば良いです。

npm run pulp:psci
> chapter3@1.0.0 pulp:psci /Users/bouzuya/.ghq/github.com/paf31/purescript-book/exercises/chapter3
> pulp psci

PSCi, version 0.10.2
Type :? for help

> import Prelude ((+))
> 1 + 2
3

> (+) 1 2
3

> let f = (+)
> f 1 2
3

> 1 `f` 2
3

Language Guide:

PureScript by Example 3.14

関数合成の話ですね。>>><<< です。それぞれ composecomposeFlipped の alias です。

Pursuit:

前述の findEntry であれば次のように書き換えられます。

head $ filter filterEntry book
(head <<< filter filterEntry) book
(filter filterEntry >>> head) book

insertEntry = Cons のように book を削れば、findEntry firstName lastName = head <<< filter filterEntry とできます。ふむふむ。

PureScript by Example 3.15

psci でためしていますね。

Show インスタンスでないと表示できない箇所は map showEntry entry のようにして回避しているようです。古い分だと (<$>) よりも読みかたの分かる map になったということでしょうか。

Pursuit:

まとめ

3 章の後半は密度が高かったです。慌てて進めたので、雑になってしまいました。

参考

次回以降の TODO

bouzuya
ぼく、ぼうずや。なさけはひとのためならず。たのしいはせいぎ。
http://bouzuya.net/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした