Last active
August 29, 2015 14:06
-
-
Save gilbert/7d23274e9cd979292ebe to your computer and use it in GitHub Desktop.
Arrow Programming Language Sketch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Library do | |
record Author do | |
fields do | |
name :: String | |
end | |
name :: Book -> String | |
name { author: authorName } = authorName | |
end | |
record Genre do | |
fields do | |
name :: String | |
end | |
end | |
record Book do | |
fields do | |
title :: String | |
genre :: Genre | |
authors = [] :: [Author] | |
end | |
hasAuthor :: Book -> Author -> Bool | |
hasAuthor book author = any (\a a.name == author) book.authors | |
end | |
booksByAuthor :: Author -> [Book] -> [Book] | |
booksByAuthor author books = do | |
findAuthor book = book.hasAuthor author | |
filter findAuthor books | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Library do | |
# A record is like a type and a module put together. | |
# You specify what fields a record has, complete with types. | |
record Author do | |
# This does two things: | |
# 1. Defines a `name` field for this record | |
# 2. Defines a getter with type Book -> String | |
fields do | |
name :: String | |
end | |
# Afterwards you can optionally specify a default implementation for `name`. | |
# That is, when we create an ad-hoc record that mixes in Author, | |
# it will receive everything explicitly defined in the record. | |
# | |
# Examples: | |
# author = { Author | name: "Alice", otherStuff: "yum" } | |
# author.name | |
# => "Alice" | |
# author.otherStuff | |
# => unknown function Author.otherStuff | |
# | |
# Note that record fields are not directly accessible. To get the value of | |
# a record field, you must deconstruct and return that field with a function, | |
# which brings us to the next line. | |
# | |
# In this next line, the brackets after `name` is deconstructing the first parameter, | |
# which is a record. Note that what's written here is the default implementation we would get if | |
# we did not define anything for `name`. In other words, the default is a getter. | |
name :: Book -> String | |
name { author: authorName } = authorName | |
end | |
record Genre do | |
# Remember the default getter? This record has equivalent functionality to Author. | |
# Note that we can also call a getter a record method. In this case, | |
# we can say we're defining the `name` method on the `Genre` record. | |
fields do | |
name :: String | |
end | |
# There are two ways to access record methods: | |
# | |
# 1. Explicitly. You can do this from anywhere, and on potentially different record types: | |
# | |
# getName :: Genre -> String | |
# getName genre = Genre.name genre | |
# | |
# 2. With inference. You can only do this when your variable is explicitly typed: | |
# | |
# getName :: Genre -> String | |
# getName genre = genre.name | |
# | |
# In these examples, the latter is equivalent to the former because in the latter we | |
# know the type of `genre` - it gets translated to `Genre.name genre` during compilation. | |
end | |
record Book do | |
fields do | |
title :: String | |
genre :: Genre | |
# Below is a default value for a field. Default values only get created when | |
# the record *type* is mixed into an ad-hoc record without the defaulted field specified. | |
authors = [] :: [Author] | |
# For example: | |
# | |
# author = { Author | title: "T", genre: { name: "G" } } | |
# | |
# This example type checks, since the `authors` field has a default value. However: | |
# | |
# author = { Author | title: "T" } | |
# | |
# This will *not* typecheck, since `genre` is without a value. | |
end | |
# A custom method on the Book record. | |
# Remember that you can call this via `Book.hasAuthor someBook a` or `someBook.hasAuthor a` | |
hasAuthor :: Book -> Author -> Bool | |
hasAuthor book author = any (\a a.name == author) book.authors | |
# | |
# Here's what the compiler will translate the above definition to: | |
# | |
# hasAuthor book author = any (\a a.name == author) book.authors | |
# hasAuthor book author = any (\a (Author.name a) == author) book.authors | |
# hasAuthor book author = any (\a (Author.name a) == author) (Book.authors book) | |
# | |
# This is possible because we know the types of both `book` and `author`. | |
end | |
booksByAuthor :: Author -> [Book] -> [Book] | |
booksByAuthor author books = do | |
findAuthor book = book.hasAuthor author | |
filter findAuthor books | |
end | |
# Alternatively: | |
# booksByAuthor :: Author -> [Book] -> [Book] | |
# booksByAuthor author books = do | |
# filter findAuthor books | |
# where | |
# findAuthor book = book.hasAuthor author | |
# end | |
end | |
# How to use a module: | |
# using Library | |
# | |
# jazz :: Library.Genre | |
# jazz = { name: "Jazz" } | |
# | |
# createLibrary :: [Library.Book] | |
# createLibrary = [{ Library.Book | title: "How to Jam", genre: jazz, authors: ["Johnny Davetrane"] }] | |
# | |
# You can be a lazy typer: | |
# using Library as L | |
# | |
# Or include types and functions into your scope: | |
# import Library (Genre, Book, booksByAuthor) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment