iyi

Playground

samples/iyi/collections.iyi

The Enumerable port, exercised — SPEC.md II.6.

samples/iyi/collections.iyi · iyi-collections.wasm · 220 KB
# The `Enumerable` port, exercised — SPEC.md II.6.
#
#   ./bin/iyi run samples/iyi/collections.iyi
#
# `std/enumerable.iyi` holds the trait. This file implements it twice and calls
# every method it defines. Both matter: a default method is only typed when it
# is called, so a trait that merely compiles has proved nothing about whether
# its bodies are sound.
#
# `Nums` and `Words` answer `Elem` differently — `Int32` and `String` — and each
# gets the same 57 default methods from one `each`. That is the claim II.6
# makes, running.

module samples/collections

import std/enumerable::{Enumerable}

pub struct Nums
  def initialize(@a : Array(Int32))
  end
end

impl Enumerable for Nums
  type Elem = Int32

  def each(& : Int32 -> Nil) : Nil
    @a.each { |x| yield x }
  end
end

pub struct Words
  def initialize(@a : Array(String))
  end
end

impl Enumerable for Words
  type Elem = String

  def each(& : String -> Nil) : Nil
    @a.each { |x| yield x }
  end
end

n = Nums.new([3, 1, 4, 1, 5])
w = Words.new(["pear", "fig", "plum", "kiwi"])

puts "to_a            #{n.to_a.join(",")}"
puts "size            #{n.size}"
puts "empty?          #{n.empty?} #{Nums.new([] of Int32).empty?}"
puts "all?            #{n.all? { |x| x > 0 }}"
puts "any?            #{n.any? { |x| x > 4 }}"
puts "none?           #{n.none? { |x| x > 9 }}"
puts "one?            #{n.one? { |x| x == 4 }}"
puts "count           #{n.count { |x| x == 1 }}"
puts "find            #{n.find { |x| x > 3 }} #{n.find { |x| x > 9 }.inspect}"
puts "index           #{n.index { |x| x == 4 }} #{n.index { |x| x == 9 }.inspect}"
puts "first?/first    #{n.first?} #{n.first}"
puts "map             #{n.map { |x| x * 2 }.join(",")}"
puts "flat_map        #{n.flat_map { |x| [x, x] }.join(",")}"
puts "map_with_index  #{n.map_with_index { |x, i| x * i }.join(",")}"
puts "select          #{n.select { |x| x > 2 }.join(",")}"
puts "reject          #{n.reject { |x| x > 2 }.join(",")}"
puts "partition       #{n.partition { |x| x > 2 }.map(&.join("+")).join(" | ")}"
puts "take/skip       #{n.take(2).join(",")} #{n.skip(3).join(",")}"
puts "take_while      #{n.take_while { |x| x < 4 }.join(",")}"
puts "skip_while      #{n.skip_while { |x| x < 4 }.join(",")}"
puts "reduce          #{n.reduce(0) { |a, x| a + x }}"
puts "each_with_index #{n.map_with_index { |x, i| i }.join(",")}"
puts "each_with_object #{n.each_with_object([] of Int32) { |e, o| o << e * 10 }.join(",")}"
puts "join            #{n.join("-")}"
puts "includes?       #{n.includes?(4)} #{n.includes?(9)}"
puts "max?/max        #{n.max?} #{n.max}"
puts "min?/min        #{n.min?} #{n.min}"
puts "sum/product     #{n.sum} #{n.product}"
puts "sum of nothing  #{Nums.new([] of Int32).sum}"
puts "tally           #{n.tally[1]}"
puts "sorted          #{n.sorted.join(",")}"
puts "max_by          #{w.max_by { |s| s.size }}"
puts "group_by        #{w.group_by { |s| s.size }[4].join(",")}"
puts "index_by        #{w.index_by { |s| s.size }[3]}"

puts "each_slice      #{n.each_slice(2).map(&.join("+")).join(" ")}"
puts "each_cons       #{n.each_cons(2).map(&.join("+")).join(" ")}"
puts "chunk_while     #{n.chunk_while { |a, b| b >= a }.map(&.join("+")).join(" ")}"
puts "first(n)        #{n.first(3).join(",")}"
puts "compact_map     #{n.compact_map { |x| x > 2 ? x * 10 : nil }.join(",")}"
puts "find_value      #{n.find_value { |x| x > 3 ? "big" : nil }}"
puts "accumulate      #{n.accumulate(0) { |a, x| a + x }.join(",")}"
puts "reduce?         #{n.reduce? { |a, x| a + x }}"
puts "in_groups_of    #{n.in_groups_of(2, 0).map(&.join("+")).join(" ")}"
puts "min_by          #{w.min_by { |s| s.size }}"
puts "max_of/min_of   #{w.max_of { |s| s.size }} #{w.min_of { |s| s.size }}"
puts "minmax          #{n.minmax[0]},#{n.minmax[1]}"
puts "tally_by        #{w.tally_by { |s| s.size }[4]}"
puts "sum_by          #{w.sum_by { |s| s.size }}"

# SPEC II.6 finding 6: one bound replaces `Indexable | Iterable | Iterator`,
# and `O::Elem` names what the other collection yields. Four numbers for
# four words: a shorter other collection is refused, as the other library
# refuses it.
puts "zip             #{Nums.new([3, 1, 4, 1]).zip(w).map { |pair| "#{pair[0]}#{pair[1]}" }.join(" ")}"

cycled = [] of Int32
Nums.new([1, 2]).cycle(2) { |x| cycled << x }
puts "cycle           #{cycled.join(",")}"

# A trait with an associated type used as a type: one function, any implementer.
def describe(e : Enumerable) : String
  "#{e.size} items"
end

puts "as a type       #{describe(n)} / #{describe(w)}"

# ---------------------------------------------------------------------------
# What this sample would report as an error
# ---------------------------------------------------------------------------
#
# * `Words.new([...]).sum` — `sum` is bounded `where Elem : Num` and
#   `String` implements `Comparable` but not `Num`. The error names the element
#   type and the bound rather than failing somewhere inside the method body.
#
# * A second `impl Enumerable for Nums` — a trait with an associated type is
#   implementable once per type, because a second answer for `Elem` would make
#   every call on `Nums` ambiguous.
#
# * `impl Num for X` with no `impl Comparable for X` before it —
#   `Num : Comparable` requires it, and the impls have to be written in
#   dependency order.
output
Press Run.

recorded with iyi 0.18.0 (built on Crystal 1.22.0-dev [2a9653052] (2026-10-18)) · Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+ · 2026-10-19

You can type in the pane: nothing here compiles, so what you type cannot run, and the page says which bytes it is running the moment the text stops being the recorded file. There is no standard input, no filesystem and no threads on this target, and one sample fails on purpose for exactly that reason.

Run executes iyi-collections.wasm, the module the compiler built from this file, in your browser. How this works

Try it on your machine

Copy the program into collections.iyi, change something, and run it. Only a compiler on your own machine can run what you wrote.

sh, after installing
$ iyi run collections.iyi