# 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.
Press Run.
The pane no longer holds samples/iyi/collections.iyi. Run executes iyi-collections.wasm, the module the compiler built from the recorded file, so your edit is not in this run: there is no iyi compiler in this page. How this works
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.
$ iyi run collections.iyi