【Haskell】llvm-hs-pureのサンプルコードで出るパターンマッチ排他警告を修正する
llvm-hs-pureのREADMEに書いてあるこちらのコードですが、GHCのincomplete-uni-patternsオプションを有効にすると警告が出ます。
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecursiveDo #-} import Data.Text.Lazy.IO as T import LLVM.Pretty -- from the llvm-hs-pretty package import LLVM.AST hiding (function) import LLVM.AST.Type as AST import qualified LLVM.AST.Float as F import qualified LLVM.AST.Constant as C import LLVM.IRBuilder.Module import LLVM.IRBuilder.Monad import LLVM.IRBuilder.Instruction simple :: IO () simple = T.putStrLn $ ppllvm $ buildModule "exampleModule" $ mdo function "add" [(i32, "a"), (i32, "b")] i32 $ \[a, b] -> mdo entry <- block `named` "entry"; do c <- add a b ret c
Pattern match(es) are non-exhaustive In a lambda abstraction: Patterns not matched: [] [_] (_:_:_:_)
\[a, b] -> mdo
から始まるラムダ式がパターンマッチのケースを網羅していないのが原因なので、とにかく警告をなんとかしたい場合は以下のようにすればOKです。
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecursiveDo #-} import Data.Text.Lazy.IO as T import LLVM.Pretty -- from the llvm-hs-pretty package import LLVM.AST hiding (function) import LLVM.AST.Type as AST import qualified LLVM.AST.Float as F import qualified LLVM.AST.Constant as C import LLVM.IRBuilder.Module import LLVM.IRBuilder.Monad import LLVM.IRBuilder.Instruction simple :: IO () simple = T.putStrLn $ ppllvm $ buildModule "exampleModule" $ mdo function "add" [(i32, "a"), (i32, "b")] i32 $ \x -> case x of [a, b] -> mdo entry <- block `named` "entry"; do c <- add a b ret c -- 一つ目のパターンにマッチしなかった場合のケースを追加 _ -> error "Error!"
ちなみに、LambdaCaseというGHC拡張を利用すると、ラムダ式をもっと簡潔に書くことができます。
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecursiveDo #-} -- GHC拡張を追加 {-# LANGUAGE LambdaCase #-} import Data.Text.Lazy.IO as T import LLVM.Pretty -- from the llvm-hs-pretty package import LLVM.AST hiding (function) import LLVM.AST.Type as AST import qualified LLVM.AST.Float as F import qualified LLVM.AST.Constant as C import LLVM.IRBuilder.Module import LLVM.IRBuilder.Monad import LLVM.IRBuilder.Instruction simple :: IO () simple = T.putStrLn $ ppllvm $ buildModule "exampleModule" $ mdo function "add" [(i32, "a"), (i32, "b")] i32 $ \case -- ここの記述がシンプルになる [a, b] -> mdo entry <- block `named` "entry"; do c <- add a b ret c _ -> error "Error!"
【Haskell】cabal runで表示される余計なメッセージを非表示にする
cabal runでHaskellプログラムを実行するとビルドの情報などが表示されますが、実行結果を標準出力として他のプログラムに渡したい場合はこれだと困ると思います。
例
ビルドが走った場合
$ echo 42 | cabal run kagla-hs Build profile: -w ghc-8.8.4 -O1 In order, the following will be built (use -v for more details): - kagla-hs-0.1.0.0 (exe:kagla-hs) --enable-profiling (file app/Main.hs changed) Preprocessing executable 'kagla-hs' for kagla-hs-0.1.0.0.. Building executable 'kagla-hs' for kagla-hs-0.1.0.0.. [1 of 1] Compiling Main ( app/Main.hs, /Users/devilune/dev/src/github.com/convcha/kagla-hs/dist-newstyle/build/x86_64-osx/ghc-8.8.4/kagla-hs-0.1.0.0/x/kagla-hs/build/kagla-hs/kagla-hs-tmp/Main.p_o ) Linking /Users/devilune/dev/src/github.com/convcha/kagla-hs/dist-newstyle/build/x86_64-osx/ghc-8.8.4/kagla-hs-0.1.0.0/x/kagla-hs/build/kagla-hs/kagla-hs ... define i32 @main() { ret i32 42 }
ビルドが不要だった場合
$ echo 99 | cabal run kagla-hs Up to date define i32 @main() { ret i32 99 }
具体的に困る例を出すと、例えばこちらの記事の例のようにLLVM IRを標準出力に出力して、それをLLVMのインタプリタに渡して実行するような場合、余計なメッセージが出力されてしまうとエラーになってしまいます。
↓Up to date
という命令なんてないよ!と怒られている
$ echo 42 | cabal run kagla-hs | lli lli: <stdin>:1:1: error: expected top-level entity Up to date ^
これを防ぐには、cabalの-v0
というオプションを付けて実行すればOKです。
$ echo 42 | cabal -v0 run kagla-hs | lli
【Haskell】cabal runでプログラムを実行する際に標準入力を渡す
プログラム
module Main where main :: IO () main = do name <- getLine putStrLn $ "Hello, " ++ name ++ "!"
実行方法
$ echo Bob | cabal run greet
これでOK。
※greet
はプログラム名
Tonbly開発日記 #2 -アーキテクチャやライブラリなどの紹介-
はじめに
絶賛開発停滞中のTonblyというQiitaクローン的なアプリについての記事第二弾。
github.com
前回の記事では開発の背景について記したが、今回はTonblyの構成について紹介する。
尚、文体が前回とまるで異なるのはキャラ付けを模索しているからだ。
アーキテクチャ
以下のような構成になっている。
(Draw.ioで作成した画像が意外と荒くて残念...)
Reactを使ったSPAで、特徴はHasuraを利用してGraphQLによるデータのやり取りを行っているところだろう。
このような構成になった背景としては、以下のテーマで何か開発してみたい、と常々思っていたという部分がある。
- できる限りサーバーサイドの処理を書きたくない
- GraphQLを使ってみたい
- No Redux構成を模索したい
1. できる限りサーバーサイドの処理を書きたくない
以前GrapeCityのイベントに登壇した際に紹介したのだが、一昨年〜昨年にかけて以下の構成でFlowlessというアサイン管理アプリを開発していた。
- フロントエンド
- TypeScript
- React
- Redux
- Redux-Saga
- バックエンド
- Scala
- Scalatra
- Skinny ORM
- PostgreSQL
アサイン管理アプリというのは、どの開発プロジェクトにいつからいつまで誰を参画させるか、という計画を管理するためのものである。
少なくともIT系の企業であればどこでも同じようなことをやっていると思うが、ひとまずWBSのようなものだと思ってもらえればよい。
アプリのUIはこのような感じだ。
Flowlessについてはほとんど触れていないが、興味があればスライドも併せて参照されたい。
そして、Flowlessを開発した際に感じたのが、「たいした処理もないのにサーバーサイドとのやり取りを書くのがとてつもなく面倒」ということだ。
UIを起点として開発していたので、
- 画面にこういう機能を追加したいからこういうデータが欲しい
- そのためにテーブルを作り
- 必要があればViewを作り
- マッピングするScalaのクラスを作り
- Scalatraのコントローラーを作り
- クライアント側で受け取るためのInterfaceを作り
- Fetchする処理を書き
- ReduxのActionやSelectorを作り
...といった感じで、ちょっとしたデータ取得ロジックを作るだけでも相当な手間がかかってしまっていた。
これはさすがにしんどいということで、次に何か作る際はGraphQLあたりを使ってうまく改善できないか、と考えていたのだが、GraphQLはGraphQLでただのクエリ言語であるので結局Resolverなどは自分で書く必要がある。
これでは仮に手間が減っても面倒なのは変わらないのでは...と悩んでいたときに見つけたのが今回採用したHasuraである。
Hasuraを簡単に表現すると、「GraphQLで実装が必要な面倒な部分を全て隠蔽してくれるラッパー」という感じだろうか。
Hasuraについては改めて記事を書きたいと思うが、結果的にHasuraの採用は大正解だった。
後述のApollo、graphql-codegenと組み合わせることにより、
- Hasuraの管理画面(Web)でテーブルを作る
- React上でGraphQLを使って欲しいデータを宣言する
なんとこれだけで必要なコードが全て手に入るようになるのだ。
これは本当に衝撃的で、(開発者体験の方の)DXもここまで来たか!と驚いてしまった。
Hasuraはサンフランシスコとバンガロールを拠点としたスタートアップで、けっこうな額の資金調達を行ったというニュースも最近見たが、本当にHasuraは素晴らしいので納得である。
余談だが、このDX向上にはReactのHooksも大いに寄与している。
Hooksを考えた(はずだったと思うが間違っていたら申し訳ない)Sebastian Markbåge氏は天才だと思う...。
2. GraphQLを使ってみたい
これはタイトルそのままなので割愛する。
3. No Redux構成を模索したい
私はReduxが好きである。
なぜなら、"Single Source of Truth"という思想と、あの"ガチャガチャした感じ"に惹かれるからだ。
今はReactコアチームに入ってアンチReduxになってしまったが、原作者であるDan Abramov氏のカリスマ性も魅力的だ。
お前は何を言っているんだと思われるかもしれないが、エンジニアだって人間なので所詮そんなものだろう...というのは冗談だが、やはり便利なので使っている。
更に言えばRedux-Sagaも好きだ。
Reduxを更にガチャガチャさせたあの感じと、全然Sagaパターンとは関係ないのにSagaと名付けちゃうところが可愛いのである。
この記事の本旨とは何も関係がないが、「Redux-Sagaは難しい」「たいていのアプリにはRedux-Sagaはオーバーキル」という風潮に対しては常々思うところがある。
Redux-Sagaはむしろシンプルではないか?
意地悪な見方をすれば、「関数を返す関数」という概念を理解する必要があるRedux Thunkの方が難しいとも言えるはずだ。
確かにSagaには様々な機能が含まれているが、それは「伸びしろ」として捉えて欲しいのだ。
単純にFetchするだけであればSagaでもThunkでも簡単に書けるが、さらに高度な機能が必要になったときにアーキテクチャを崩さずに対応できるというメリットがSagaにはある。
前置きが長くなったが、とにかくReduxを気に入っている...がそれはそれとして、やはり他の手段も試して模索していきたいとは思っていた。
(Flowless開発時には存在していなかったので試していないが)Redux Toolkitによってボイラープレートが大幅に改善されたとはいえ、やはりコーディング量やファイル移動が多かったりと辛い部分がある。
(Ducksパターンはスケールしないので規模が大きくなると最終的にファイルは分割することになると思う)
そこでステート管理の代替候補としてあがるのは2020年5月現在ではこんなところだろう。
- 何も使わずにProp Drillingで頑張る
- Context + useContext
- Apollo
- MobX
- Recoil
この中から、今回は今のところProp Drillingで頑張っている。
(Apolloでのローカルステート管理は行っていない)
今後グローバルなStateが必要になってきた場合はApolloかRecoilで実現するつもりだが、現在話題沸騰中のRecoilは"これでいいんだよこれで"感があり期待している。
その他ライブラリなどについて
めぼしいものはこんなところだ。
Material-UI
UIフレームワークはMaterial-UIを採用した。
前述のFlowlessで採用したMicrosoftのFluent UI(旧Office UI Fabric React)も素晴らしかったのだが、もう少し業務アプリっぽくない見た目にしたかったのと、以前は煩雑だったスタイル管理やTypeScriptの型合わせが改善されているようだったので、それを確認するためにもMaterial-UIを選択した。
上記の課題も解決されており結果的には満足しているが、欲を言えばデフォルトのCSS in JSライブラリをJSSではなくEmotionかstyled-componentsにして欲しい。
(JSSの良さが私にはわからないのだ...)
世の中には数多のUIフレームワークが存在するが、メンテされていなかったり、コンポーネントが充実していなかったりでなかなか難しい。
Apollo
自分でfetchを使ってGraphQLリクエストを送るのはさすがにないだろう、ということで定番のApolloを採用した。
Hooksも充実しており、ReactでGraphQLやるならこれでいいんだろう、という感じではあるが、キャッシュ周りの思想だけは共感ができなかった。
Apolloあるあるだと思うが、データ更新時のキャッシュ更新を忘れているせいで画面が更新されない、そのキャッシュ更新処理自体はわりと書くのが面倒、という部分があり、最初は混乱してしまった。
(最終的にキャッシュは無効にした...)
画面の表示速度、UXが命のサイトであればこのような機構が必要なのだろうが、そんなレベルのものを作っているわけではないので邪魔なだけであった。
graphql-codegen
クライアント側からGraphQLを叩くためのコードを生成してくれるツール。
これとTypeScript、Apolloとの組み合わせが凄まじく便利で、GraphQLのquery、mutationを書くと、それをApolloから叩くためのHooksをTypeScriptの型定義含めて自動で生成してくれるのだ。
自動生成コードは全てのquery、mutationに対して個別に生成されるので、Apollo単体のときのようにuseQuery
やuseMutaion
にGraphQL文を渡す必要がない。
文章では伝わりづらいと思うので例を挙げる。
Apollo単体の場合
interface RocketInventory { id: number; model: string; year: number; stock: number; } interface RocketInventoryData { rocketInventory: RocketInventory[]; } interface RocketInventoryVars { year: number; } const GET_ROCKET_INVENTORY = gql` query getRocketInventory($year: Int!) { rocketInventory(year: $year) { id model year stock } } `; export function RocketInventoryList() { const { loading, data } = useQuery<RocketInventoryData, RocketInventoryVars>( GET_ROCKET_INVENTORY, { variables: { year: 2019 } } ); return <div>...</div>; }
// 1. 型定義が不要(graphql-codegenが生成する) // 2. GraphQL文の変数定義が不要 // (graphql-codegenが生成するので使いたくなったときでも大丈夫) gql` query getRocketInventory($year: Int!) { rocketInventory(year: $year) { id model year stock } } `; export function RocketInventoryList() { // そのquery専用のHooksが用意される // (引数や結果の型定義も自動生成&推論される!) const { loading, data } = useGetRocketInventoryQuery({ variables: { year: 2019 }, }); return <div>...</div>; }
記述量が段違いなのがわかると思う。
しかも自動生成なのでGraphQLクエリの内容を変えれば各種型や関数も再生成される。
本当に"欲しいものを書くだけ"である。
TOAST UI Editor for React
Markdownエディタを埋め込むためのReactコンポーネント。
Markdownエディタ、WYSIWYGエディタも色々探したが、これが決定版だと思っている。
機能豊富でたいていのことはでき、カスタマイズする口も用意されているのが良い。
Tonblyは技術者以外の利用も想定しているので、WYSIWYGモードが用意されているのは嬉しい限りだ。
v2にメジャーバージョンアップしてMarkdownパーサーの刷新など様々な改善が施されたようだが、とりあえずバージョンを上げただけでまだ追従できていないのでこれから取り組んでいきたい。
Ky
fetchをラップして使いやすくしたHTTPクライアント。
「そうそうこういうのが欲しいんだよ」という感じのシンプルなAPIなのだが、これはコードを見ていただいた方が早いだろう。
// GET const result = await ky.get("https://example.com").json(); // POST await ky.post('https://example.com', {json: {foo: true}})
サーバー側(Express)ではNode版のGotを使っている。
この手のライブラリはAxiosなど色々あると思うが、これで何も問題が無いし、JavaScriptマスター?のSindre Sorhus氏作ということもありこちらを採用した。
さいごに
だいぶ長い記事になってしまったが、参考になれば幸いである。
Hasuraの詳細など、開発で得た知見はまだまだたくさんあるので小出しにしていければと思う。
(そもそもアプリ自体の開発を進めないと...)
フィードバックや質問があればコメント欄かTwitterで是非。
Scrapboxの外部リンク記法でクリップボードにURLをコピーするブックマークレット
適当なページをブックマークした後にURLを以下のコードに置き換えればOK。
javascript:(()=>{const tmp = document.createElement('p');const pre = document.createElement('p');pre.style.userSelect = 'auto';tmp.appendChild(pre).textContent = '['+document.title.replace(/\s*[\[\]]\s*/g,' ')+' '+location.href+']';document.body.appendChild(tmp);document.getSelection().selectAllChildren(tmp);document.execCommand('copy');document.body.removeChild(tmp);})();
あとは登録したブックマークのアイコンをクリックするだけで、現在表示しているページのURLがScrapboxの外部リンク記法でクリップボードにコピーされます。
圧縮前のコード
(() => { const tmp = document.createElement("p"); const pre = document.createElement("p"); pre.style.userSelect = "auto"; tmp.appendChild(pre).textContent = `[${document.title.replace( /\s*[\[\]]\s*/g, " " )} ${location.href}]`; document.body.appendChild(tmp); document.getSelection().selectAllChildren(tmp); document.execCommand("copy"); document.body.removeChild(tmp); })();
参考
①Scrapbox用の外部リンク記法を取得するbookmarklet - 橋本商会
②ページのURLをいい感じにクリップボードにコピーするブックマークレット - Qiita
①の方法でも十分なのですが、ダイアログ表示 -> 自分でコピー という手順が面倒だったので②と組み合わせた感じです。
TOAST UIのMarkdownエディタのReact版にTypeScriptの型が付いたよ
TOAST UIというWebのUIツールキットにMarkdownエディタがあるのですが、これがかなり便利で、以前記事を書いたQiitaクローンで使っています。
↓こんなエディタが簡単に作れます
ただ、このエディタのReact版はTypeScriptの型定義がなかったので自分で型を付けて使っていました...が今日最新版にアップデートしたら元々のリポジトリがdeprecated化&ソースがエディタ本体のリポジトリに移動&型定義が作成されていました。
こちらが元のリポジトリ
github.com
こちらが移動後のリポジトリ
github.com
npmのパッケージ自体は@toast-ui/react-editor
から変わっていないので単純にアップデートすればOKです。
以前はあまりメンテナンスされていない感じだったのですが、本体に取り込まれたのもあり、今のところ活発にメンテナンスされているようです。
今後どうなるかは怪しい部分もありますが、WebでMarkdownエディタを組み込むのであればオススメのライブラリです。
ちなみにVue.js版もあります。
github.com
供養
これを書くために記事をしたためたのですが、このエディタは普通に使おうと思うと
<div id="editor"></div>
const editor = new toastui.Editor({ el: document.querySelector('#editor'), previewStyle: 'vertical', height: '500px', initialValue: content });
こんな感じのコードになるのですが、QiitaクローンはReact製なのでこんなことはしたくないわけです。
自分でラップしてもいいんですが、前述の通り公式のReactコンポーネントが用意されているのでそれを使っていました&自分で型を付けていました。
せっかく作ったので、役に立つことはないと思いますがコードを置いておきます。
declare module "@toast-ui/react-editor";
// Editor.tsx import { Editor as TuiEditor, Viewer as TuiViewer } from "@toast-ui/react-editor"; import "codemirror/lib/codemirror.css"; import "highlight.js/styles/gml.css"; import React from "react"; import "tui-editor/dist/tui-editor-contents.min.css"; import "tui-editor/dist/tui-editor-extChart"; import "tui-editor/dist/tui-editor-extColorSyntax"; import "tui-editor/dist/tui-editor-extScrollSync"; import "tui-editor/dist/tui-editor-extTable"; import "tui-editor/dist/tui-editor-extUML"; import "tui-editor/dist/tui-editor.min.css"; interface Hooks { previewBeforeHook: (...args: any[]) => void; addImageBlobHook: ( fileOrBlob: File | Blob, callback: (...args: any[]) => void, source: string ) => void; } interface ToMarkOptions { gfm?: boolean; renderer?: any; } interface Converter { getMarkdownitHighlightRenderer(): markdownit; initHtmlSanitizer(): void; toHTML(makrdown: string): string; toHTMLWithCodeHightlight(markdown: string): string; toMarkdown(html: string, toMarkdownOptions: ToMarkOptions): string; } interface EditorProps { height?: string; minHeight?: string; initialValue?: string; previewStyle?: "tab" | "vertical"; initialEditType?: "markdown" | "wysiwyg"; onLoad?: (...args: any[]) => void; onChange?: (...args: any[]) => void; onStateChange?: (...args: any[]) => void; onFocus?: (...args: any[]) => void; onBlur?: (...args: any[]) => void; hooks?: Array<Hooks>; language?: string; useCommandShortcut?: boolean; useDefaultHTMLSanitizer?: boolean; codeBlockLanguages?: Array<string>; usageStatistics?: boolean; toolbarItems?: string; hideModeSwitch?: boolean; exts?: Array<any>; customConvertor?: Converter; placeholder?: string; previewDelayTime?: string; linkAttribute?: any; } interface ViewerProps { initialValue?: string; onLoad?: (...args: any[]) => void; onChange?: (...args: any[]) => void; onStateChange?: (...args: any[]) => void; onFocus?: (...args: any[]) => void; onBlur?: (...args: any[]) => void; hooks?: Array<Hooks>; exts?: Array<any>; } export const Editor = React.forwardRef<any, EditorProps>((props, ref) => ( <TuiEditor ref={ref} usageStatistics={false} exts={[ { name: "chart", minWidth: 100, maxWidth: 600, minHeight: 100, maxHeight: 300 }, "scrollSync", "colorSyntax", "uml", "mark", "table" ]} {...props} /> )); export const Viewer = React.forwardRef<any, ViewerProps>((props, ref) => ( <TuiViewer ref={ref} exts={[ { name: "chart", minWidth: 100, maxWidth: 600, minHeight: 100, maxHeight: 300 }, "scrollSync", "colorSyntax", "uml", "mark", "table" ]} {...props} /> ));
見ての通り、結局ラップしてるだけじゃねーか、という感じではあります...。
【参考になりません】Haskellの型クラスをScalaに移植する #1
モナドについて学ぶべくすごいH本を読んだので復習として型クラスをScalaに移植してみます。
本と同じようにFunctor -> Applicative Functor -> Monadという順番で進めていこうと思います。
ちなみにScalaは趣味で少し書いたことがある程度です。
Haskellはこの本を読んだだけで、コードは一切書いたことがありません。
今回はまずFunctor型クラスと、そのListインスタンスを実装しようかと。
まずは何も見ずに自分で考えて実装した後、可能であればScala界の代表的な関数型ライブラリである(合ってます?)ScalazとCatsの実装を見て答え合わせします。
※疑問だらけでめちゃくちゃ中途半端な状態で終わります
※書いてあることは高確率で間違っています
※文中の疑問や誤りについてコメントいただけると嬉しいです
回答
コード
object Main { trait Functor[F[_]] { def fmap[A, B](a: A => B, fa: F[A]): F[B] } object Functor { implicit object ListFunctor extends Functor[List] { override def fmap[A, B](a: A => B, fa: List[A]): List[B] = fa.map(a) } } def exec()(implicit f: Functor[List]): Unit = { val r = f.fmap[Int, Int](a => a * 3, List(1, 2, 3)) print(r) } def main(args: Array[String]): Unit = { exec() } }
気になる点
def exec()(implicit f: Functor[List])
コンパイルが通らないので、ここでFunctorの型パラメータをList決め打ちにしてますが、これおかしいですよね?
何のための型クラスなんだという気がするのですが。
fmapに渡した型に合わせてインスタンスを引っ張ってきて欲しい。
でも例えばこうするとコンパイルエラーになるんですよね。
def exec[A]()(implicit f: Functor[A]): Unit = { // A[Int]が欲しいのにList[Int]になってるよ!と怒られる val r = f.fmap[Int, Int](a => a * 3, List(1, 2, 3)) print(r) }
というか、こんなことやるんだったら、わざわざimplicitパラメータでインスタンスを受け取らなくても、ListFunctor.fmap[Int, Int](a => a * 3, List(1, 2, 3))
でいいんですよね。
いや、でもこれこういうものなのか...?
「ListはFunctorだよ!」ということをScala君に教えてあげられれば解決できるような気がするんですが、何か根本的に書き方が間違っているのか?
それとも、HaskellとかOCamlみたいに推論を頑張ってくれる言語じゃないと無理とか?
疑問は尽きませんが、頑張りすぎると続かないので答えを見ます。
追記:List値をベタ書きで渡しているからインスタンスもListを要求されるとかそういうことなのか?うーむそもそも何がやりたいのかわからなくなってきた。
答え合わせ
Scalaz
Functorの定義はこうなってました。
trait Functor[F[_]] extends InvariantFunctor[F] { self => ... /** Lift `f` into `F` and apply to `F[A]`. */ def map[A, B](fa: F[A])(f: A => B): F[B] ... }
これだけ見ると私のと実質同じですね。
(継承してるInvariantFunctorが何なのか不明ですがそこまでは追いたくない...)
ああ、でも私のは引数をカリー化してないですね。忘れてました。
後は、Haskellと比べるとScalazの方の引数はFunctor値が先に来てますが、文化、スタイルの違いですかね?
-- HaskellのFunctor定義(本に載ってたやつなのでたぶん古い) class Functor f where fmap :: (a -> b) -> f a -> fb
でもラムダ式にすることを考えると関数は後の方が良い気がします。
逆にHaskellの方はこれでいいのか疑問ですが、なんかそのあたりもすごいH本に書いてあった気がするが記憶が...。
さて、Listインスタンスの方なんですが、これでしょうか。
trait ListInstances extends ListInstances0 { implicit val listInstance: Traverse[List] with MonadPlus[List] with Alt[List] with BindRec[List] with Zip[List] with Unzip[List] with Align[List] with IsEmpty[List] with Cobind[List] = new Traverse[List] with MonadPlus[List] with Alt[List] with IterableBindRec[List] with Zip[List] with Unzip[List] with Align[List] with IsEmpty[List] with Cobind[List] with IterableSubtypeFoldable[List] with Functor.OverrideWiden[List] { ... }
https://github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/std/List.scala
Functor.OverrideWiden[List]
というFunctor的なものを継承(ミックスイン?)してるので合ってそうです。
(OverrideWidenが何なのかは不明以下略)
で、これを見て「ああ、やっぱりそういうパターンか」とガッカリしたんですが、ここにはmapの実装はありませんでした。
で、本当に申し訳ないのですが疲れたので今回はここまでにします...。
言い訳としては、諸事情で固定回線がなくなってしまったのでデバッグしてコードを追うことがやりづらくモゴモゴ。
Cats
まずはFunctorの定義です。
@typeclass trait Functor[F[_]] extends Invariant[F] { self => def map[A, B](fa: F[A])(f: A => B): F[B] ... }
これも私のコードおよびScalazとほとんど同じですね。
というかここは変えようがないのか。
Functorを直接ミックスインしてないので推測ですが、Listインスタンスはこれっぽいです。
trait ListInstances extends cats.kernel.instances.ListInstances { implicit val catsStdInstancesForList : Traverse[List] with Alternative[List] with Monad[List] with CoflatMap[List] with Align[List] = new Traverse[List] with Alternative[List] with Monad[List] with CoflatMap[List] with Align[List] { override def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f)
こっちはmapがありますね!
内容も私のものと同じです。
やはり問題は定義より使い方なのか...?
こっちも今回はここまでにします。
あと気になったのが、ScalazにもCatsにもインスタンスとは別にsyntaxなるパッケージがあって、ListOps
みたいな感じでインスタンスに対応する?クラスが定義してあるのですが、あれが何なのか謎。
さいごに
いかがでしたか?(棒)
今回の反省点ですが、そもそもScalazやCatsを一度も使ったことがないのにいきなりコードリーディングするのは無理がありました。
あと、Scala自体の知識も足りてなさ過ぎですね。
でも、モナドは無理そうなので本を読みましたが、本当は教科書で勉強するよりこういう過程で学んでいくのが好きなんですよね...。
次は、パッと見た感じScalazより読みやすそうなCatsについてドキュメント含めてちゃんと調べるか、いきなりライブラリ読まんでもネットにサンプルが転がっていると思うのでそれを漁る感じかなと思ってます。
参考
Functor, Applicative, Traversable, Monad について
Extensible Effects in Scala
A Gentle Introduction to Haskell: Standard Classes