Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Gleam #415

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions gleam/.github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: test

on:
# push:
# branches:
# - master
# - main
# pull_request:
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: "26.0.2"
gleam-version: "0.32.2"
rebar3-version: "3"
# elixir-version: "1.15.4"
- run: gleam format --check src test
- run: gleam deps download
- run: gleam test
4 changes: 4 additions & 0 deletions gleam/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.beam
*.ez
build
erl_crash.dump
22 changes: 22 additions & 0 deletions gleam/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# related

[![Package Version](https://img.shields.io/hexpm/v/related)](https://hex.pm/packages/related)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/related/)

## Quick start

```sh
gleam run # Run the project
gleam test # Run the tests
gleam shell # Run an Erlang shell
```

## Installation

If available on Hex this package can be added to your Gleam project:

```sh
gleam add related
```

and its documentation can be found at <https://hexdocs.pm/related>.
20 changes: 20 additions & 0 deletions gleam/gleam.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name = "related"
version = "0.1.0"
gleam = ">= 0.32.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
#
# description = ""
# licences = ["Apache-2.0"]
# repository = { type = "github", user = "username", repo = "project" }
# links = [{ title = "Website", href = "https://gleam.run" }]

[dependencies]
gleam_stdlib = "~> 0.32"
gleam_json = "~> 0.7"
simplifile = "~> 0.2"
birl = "~> 0.17"

[dev-dependencies]
gleeunit = "~> 1.0"
19 changes: 19 additions & 0 deletions gleam/manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# This file was generated by Gleam
# You typically do not need to edit this file

packages = [
{ name = "birl", version = "0.17.0", build_tools = ["gleam"], requirements = ["ranger", "gleam_stdlib"], otp_app = "birl", source = "hex", outer_checksum = "AEA55D2329E13E72CB7FCC0FBAE640C453E6C9BF45B8D9DB4D7457F7F5C18F49" },
{ name = "gleam_json", version = "0.7.0", build_tools = ["gleam"], requirements = ["thoas", "gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "CB405BD93A8828BCD870463DE29375E7B2D252D9D124C109E5B618AAC00B86FC" },
{ name = "gleam_stdlib", version = "0.32.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "07D64C26D014CF570F8ACADCE602761EA2E74C842D26F2FD49B0D61973D9966F" },
{ name = "gleeunit", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D3682ED8C5F9CAE1C928F2506DE91625588CC752495988CBE0F5653A42A6F334" },
{ name = "ranger", version = "0.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "653A15D0C73E75AD4EC6DE40478F0EBBA1157D0F5BB16FDD454D0C1129C32D41" },
{ name = "simplifile", version = "0.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "856DD0CD5FEEB464FB32522F6C9C51F5DE1398799C17028D3645EDC4B732E7DB" },
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
]

[requirements]
birl = { version = "~> 0.17" }
gleam_json = { version = "~> 0.7" }
gleam_stdlib = { version = "~> 0.32" }
gleeunit = { version = "~> 1.0" }
simplifile = { version = "~> 0.2" }
69 changes: 69 additions & 0 deletions gleam/src/post.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import gleam/dynamic.{type DecodeError, type Decoder, type Dynamic}
import gleam/json.{type Json}
import gleam/set.{type Set}
import gleam/result

pub type Post {
Post(id: PostId, title: String, tags: Set(Tag))
}

pub opaque type Tag {
Tag(string: String)
}

pub opaque type PostId {
PostId(string: String)
}

pub type RelatedPost {
RelatedPost(id: PostId, tags: List(Tag), related: List(Post))
}

// JSON ENCODERS ---------------------------------------------------------------

pub fn related_to_json(related_post: RelatedPost) -> Json {
json.object([
#("_id", id_to_json(related_post.id)),
#("tags", json.array(related_post.tags, of: tag_to_json)),
#("related", json.array(related_post.related, of: post_to_json)),
])
}

fn post_to_json(post: Post) -> Json {
let Post(id, title, tags) = post
json.object([
#("_id", id_to_json(id)),
#("title", json.string(title)),
#("tags", json.array(set.to_list(tags), of: tag_to_json)),
])
}

fn tag_to_json(tag: Tag) -> Json {
let Tag(tag) = tag
json.string(tag)
}

fn id_to_json(id: PostId) -> Json {
let PostId(id) = id
json.string(id)
}

// DECODERS --------------------------------------------------------------------

pub fn decode(data: Dynamic) -> Result(Post, List(DecodeError)) {
let post_id_decoder = fn(data) { result.map(dynamic.string(data), PostId) }
let tag_decoder = fn(data) { result.map(dynamic.string(data), Tag) }
dynamic.decode3(
Post,
dynamic.field("_id", of: post_id_decoder),
dynamic.field("title", of: dynamic.string),
dynamic.field("tags", of: decode_set_from_list(tag_decoder)),
)(data)
}

fn decode_set_from_list(with decoder: Decoder(a)) -> Decoder(Set(a)) {
fn(data) {
dynamic.list(of: decoder)(data)
|> result.map(set.from_list)
}
}
64 changes: 64 additions & 0 deletions gleam/src/related.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import gleam/dynamic
import gleam/io
import gleam/int
import gleam/list
import gleam/bool
import gleam/set
import gleam/pair
import gleam/float
import simplifile.{read, write}
import birl/time
import gleam/json
import post.{type Post, type RelatedPost, RelatedPost}

fn to_related_post(post: Post, posts: List(Post)) -> RelatedPost {
RelatedPost(
post.id,
set.to_list(post.tags),
similar_posts(to: post, among: posts),
)
}

fn similar_posts(to post: Post, among posts: List(Post)) -> List(Post) {
let other_posts_with_similarity = {
use other_post <- list.filter_map(posts)
use <- bool.guard(when: other_post.id == post.id, return: Error(Nil))
let similarity = count_shared_tags(post, other_post)
Ok(#(similarity, other_post))
}
take_top_n(other_posts_with_similarity)
}

fn count_shared_tags(one: Post, other: Post) -> Int {
set.size(set.intersection(one.tags, other.tags))
}

const top_n = 5

fn take_top_n(list: List(#(Int, a))) -> List(a) {
list.sort(list, fn(one, other) { int.compare(other.0, one.0) })
|> list.take(top_n)
|> list.map(pair.second)
}

pub fn main() {
let assert Ok(raw_posts) = read("../posts.json")
let assert Ok(posts) =
json.decode(raw_posts, using: dynamic.list(post.decode))

let start = time.monotonic_now()
let related_posts =
posts
|> list.take(1)
|> list.map(to_related_post(_, posts))
let end = time.monotonic_now()

let took = float.to_string(int.to_float(end - start) /. 1000.0)
io.println("Processing time (w/o IO): " <> { took } <> "ms")

let all_related_json =
json.array(related_posts, of: post.related_to_json)
|> json.to_string

let assert Ok(_) = write(all_related_json, "../related_posts_gleam.json")
}
12 changes: 12 additions & 0 deletions gleam/test/related_test.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import gleeunit
import gleeunit/should

pub fn main() {
gleeunit.main()
}

// gleeunit test functions end in `_test`
pub fn hello_world_test() {
1
|> should.equal(1)
}