suzuki
Agreed. And if you prefer methods to functions, you can define methods on such a function type! In my LINQ in Go (https://github.com/nukata/linq-in-go), I define a higher order function type:
type Enumerator[T any] func(yield func(element T))
and several methods on it, for example, // Where creates an Enumerator which selects elements by appling
// predicate to each of them.
func (loop Enumerator[T]) Where(predicate func(T) bool) Enumerator[T] {
return func(yield func(T)) {
loop(func(element T) {
if predicate(element) {
yield(element)
}
})
}
}
Naturally, if you need another type parameter for a method, you must define it as a function. For example, // Select creates an Enumerator which applies f to each of elements.
func Select[T any, R any](f func(T) R, loop Enumerator[T]) Enumerator[R] {
return func(yield func(R)) {
loop(func(element T) {
value := f(element)
yield(value)
})
}
}
Now, with the function which converts a slice to an Enumerator, // From creates an Enumerator from a slice.
func From[T ~[]E, E any](x T) Enumerator[E] {
return func(yield func(E)) {
for _, element := range x {
yield(element)
}
}
}
you can write the following: seq := Select(func(e int) int { return e + 100 },
From([]int{
7, 8, 9,
})).Where(func(e int) bool {
return e%2 != 0
})
seq(func(e int) {
Println(e)
})
// Output:
// 107
// 109 class Obj
end
# Value operated by Scheme
alias Val = Nil | Obj | Bool | String | Int32 | Float64 | BigInt
to define Cons Cell of Scheme: # Cons cell
class Cell < Obj
include Enumerable(Val)
getter car : Val # Head part of the cell
property cdr : Val # Tail part of the cell
def initialize(@car : Val, @cdr : Val)
end
...
end # Cell
Note that you see generics, Enumerable(Val), and constructor arguments with '@' in the excerpt above.As for performance, Crystal is faster than Ruby 8.6 times as interpreter and 39.4 times as compiler [3]. You can use Crystal as a superfast (and typed) Ruby interpreter, in a sense.
[1] https://github.com/nukata/little-scheme-in-ruby [2] https://github.com/nukata/little-scheme-in-crystal [3] https://github.com/nukata/little-scheme#performance
[1] https://en.wikipedia.org/wiki/Moral_rights [2] http://www.japaneselawtranslation.go.jp/law/detail_main?id=2... (Copyright Act of Japan) Subsection 2 Moral Rights of Authors (Articles 18 to 20)
type Enumerator(type T) func(yield func(element T))
and the "Select" method can be written as func (loop Enumerator(T)) Select(f func(T) T) Enumerator(T) {
return func(yield func(T)) {
loop(func(element T) {
value := f(element)
yield(value)
})
}
}
You can call this method with type-safety as follows. Yay! func main() {
squares := Range(1, 3).Select(func(x int) int { return x * x })
squares(func(num int) {
fmt.Println(num)
})
}
// Output:
// 1
// 4
// 9
See https://go2goplay.golang.org/p/b0ugT68QAy2 for the complete code.And, for generality, you should write the method actually as follows.
func (loop Enumerator(T)) Select(type R)(f func(T) R) Enumerator(R) {
return func(yield func(R)) {
loop(func(element T) {
value := f(element)
yield(value)
})
}
}
However, you will get the error message then: type checking failed for main
prog.go2:17:33: methods cannot have type parameters
According to https://go.googlesource.com/proposal/+/refs/heads/master/des... this seems an intended restriction:> Although methods of a generic type may use the type's parameters, methods may not themselves have additional type parameters. Where it would be useful to add type arguments to a method, people will have to write a suitably parameterized top-level function.
> This is not a fundamental restriction but it complicates the language specification and the implementation.
For now, we have to write it as https://go2goplay.golang.org/p/mGOx3SWiFXq and I feel it rather inelegant. Good grief
# Cons cell
class Cell
include Enumerable
attr_reader :car
attr_accessor :cdr
def initialize(car, cdr)
@car = car
@cdr = cdr
end
# Yield car, cadr, caddr and so on, à la for-each in Scheme.
def each
j = self
begin
yield j.car
j = j.cdr
end while Cell === j
j.nil? or raise ImproperListException, j
end
end # Cell
and
https://github.com/nukata/little-scheme-in-crystal/blob/v0.2... # Cons cell
class Cell < Obj
include Enumerable(Val)
getter car : Val # Head part of the cell
property cdr : Val # Tail part of the cell
def initialize(@car : Val, @cdr : Val)
end
# Yield car, cadr, caddr and so on, à la for-each in Scheme.
def each
j = self
loop {
yield j.as(Cell).car
j = j.as(Cell).cdr
break unless Cell === j
}
raise ImproperListException.new(j) unless j.nil?
end
end # Cell
and they will make the point clear.
Ruby and Crystal are different languages, but you can translate your code from Ruby to Crystal line by line fairly easily.For the performance boost, see https://github.com/nukata/little-scheme/tree/v1.3.0#performa... which shows times to solve 6-Queens on a meta-circular Scheme as follows:
* Crystal 0.34.0: crystal build --release scm.cr: 2.15 sec.
* Crystal 0.34.0: crystal scm.cr: 9.88 sec.
* Ruby 2.3.7: ruby scm.rb: 84.80 sec.
Compiled (and complex enough) Crystal code runs 39 times faster than the equivalent Ruby code in this case.
The release notes of Crystal 0.34 say "Having as much as possible portable code is part of the goal of the std-lib. One of the areas that were in need of polishing was how Errno and WinError were handled. The Errno and WinError exceptions are now gone, and were replaced by a new hierarchy of exceptions." So I have modified
rescue ex: Errno
raise ErrorException.new(ex.message, NONE) if ex.errno == Errno::EPIPE
to rescue ex: IO::Error
raise ErrorException.new(ex.message, NONE) if ex.os_error == Errno::EPIPE
though it is still dependent on POSIX. >_<Go ≈ Java ≈ Crystal(Compiled) ≈ SBCL > C# ≈ PyPy > TypeScript(Node.js) ≈ Crystal ≈ Dart >> PHP > Python ≈ Ruby
[1] https://github.com/nukata/little-scheme/blob/v1.2.0/examples... [2] https://github.com/nukata/little-scheme/blob/v1.2.0/scm.scm [3] https://github.com/nukata/little-scheme/tree/v1.2.0#performa...
Here is a self-contained excerpt:
type Any = interface{}
type Enumerator func(yield func(element Any))
// Select creates an Enumerator which applies f to each of elements.
func (loop Enumerator) Select(f func(Any) Any) Enumerator {
return func(yield func(Any)) {
loop(func(element Any) {
value := f(element)
yield(value)
})
}
}
// Range creates an Enumerator which counts from start
// up to start + count - 1.
func Range(start, count int) Enumerator {
end := start + count
return func(yield func(Any)) {
for i := start; i < end; i++ {
yield(i)
}
}
}
Now you can write the following: squares := Range(1, 10).Select(func(x Any) Any { return x.(int) * x.(int) })
squares(func(num Any) {
Println(num)
})
// Output:
// 1
// 4
// 9
// 16
// 25
// 36
// 49
// 64
// 81
// 100
I'd say it is so elegant in Go!Show HN: A Little Scheme in TypeScript
Show HN: Cyg-Git Makes Cygwin's Git and GCC Usable from Go
$ cat yin-yang-puzzle.scm
;; The yin-yang puzzle
;; cf. https://en.wikipedia.org/wiki/Call-with-current-continuation
((lambda (yin)
((lambda (yang)
(yin yang))
((lambda (cc)
(display '*)
cc)
(call/cc (lambda (c) c)))))
((lambda (cc)
(newline)
cc)
(call/cc (lambda (c) c))))
;; => \n*\n**\n***\n****\n*****\n******\n...
$ little-scheme-in-go yin-yang-puzzle.scm | head
*
**
***
****
*****
******
*******
********
*********
$Show HN: Scheme with first-class call/cc in 700 lines of Go code
Show HN: Tower of Meta-Circular Interpreters of Scheme
How to Implement Scheme with Call/cc in Python
Show HN: Wokring with Files in Golang LINQ
Show HN: Scheme with first-class continuations in 300 lines of Python
The LINQ code in C# found at https://docs.microsoft.com/dotnet/api/system.linq.enumerable... can be written in Go with this implementation as follows:
// Generate a sequence of integers from 1 to 10 and then select their squares.
squares := Range(1, 10).Select(func(x Any) Any { return x.(int) * x.(int) })
squares(func(num Any) {
fmt.Println(num)
})
Here the function Range is defined as follows: // Range creates an Enumerator which counts from start
// up to start + count - 1.
func Range(start, count int) Enumerator {
end := start + count
return func(yield func(Any)) {
for i := start; i < end; i++ {
yield(i)
}
}
}
Note that Enumerator is just a function type defined as func(func(Any)).
The space complexity is O(1) and you can yield values infinitely if you want.Show HN: An Experimental 'LINQ to Objects' in Go
/// <summary>Evaluate a Lisp expression in an environment.</summary>
public object Eval(object x, Cell env) {
try {
for (;;) {
switch (x) {
case Arg xarg:
return xarg.GetValue(env);
case Sym xsym:
try {
return Globals[xsym];
} catch (KeyNotFoundException) {
throw new EvalException("void variable", x);
}
case Cell xcell: