Skip to content

Commit 51efc27

Browse files
committed
feat(v2): generics + typed pipelines
1 parent fca6d10 commit 51efc27

28 files changed

Lines changed: 1673 additions & 6 deletions

README.md

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,85 @@ Go already has great error handling, but sometimes you want to express flows as
2727

2828
## Install
2929

30-
Import the versioned package:
30+
31+
### v2 (default)
32+
33+
**λ v2 is a major rewrite** with generics + typed pipelines.
34+
35+
- Import:
36+
37+
```go
38+
import λ "github.com/4thel00z/lambda/v2"
39+
```
40+
41+
- This is a **hard cut**: **no compatibility layer** and **no migration path** from `v1`.
42+
43+
### v1
44+
45+
Import the default versioned package:
3146

3247
```go
3348
import λ "github.com/4thel00z/lambda/v1"
3449
```
3550

3651
## Quickstart
3752

53+
```go
54+
package main
55+
56+
import (
57+
"os"
58+
59+
λ "github.com/4thel00z/lambda/v2"
60+
)
61+
62+
func main() {
63+
content := λ.Slurp(os.Stdin).String().Must()
64+
_ = content
65+
}
66+
```
67+
68+
```go
69+
package main
70+
71+
import (
72+
"context"
73+
"os"
74+
75+
λ "github.com/4thel00z/lambda/v2"
76+
)
77+
78+
func main() {
79+
λ.Get("https://example.com").
80+
Do(context.Background()).
81+
Slurp().
82+
WriteToWriter(os.Stdout)
83+
}
84+
```
85+
86+
```go
87+
package main
88+
89+
import (
90+
"fmt"
91+
92+
λ "github.com/4thel00z/lambda/v2"
93+
)
94+
95+
type MagicSpell struct {
96+
Name string `json:"name"`
97+
Power int `json:"power"`
98+
}
99+
100+
func main() {
101+
spell := λ.FromJSON[MagicSpell](λ.Open("magic.json").Slurp()).Must()
102+
fmt.Println(spell.Name, spell.Power)
103+
}
104+
```
105+
106+
<details>
107+
<summary><strong>Show v1 examples</strong></summary>
108+
38109
### Read all lines from stdin
39110

40111
```go
@@ -109,11 +180,6 @@ func main() {
109180
}
110181
```
111182

112-
## Examples
113-
114-
<details>
115-
<summary><strong>Show examples</strong></summary>
116-
117183
### Functional conditionals
118184

119185
You never need to check an error with an if clause again. Instead you can define the flow as functional chain,
@@ -280,6 +346,14 @@ go run ./example/json
280346
go run ./example/http
281347
```
282348

349+
v2 examples live in `v2/example/`:
350+
351+
```bash
352+
go run ./v2/example/json
353+
go run ./v2/example/http
354+
go run ./v2/example/conditionals
355+
```
356+
283357
## Development
284358

285359
```bash

v2/README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# λ v2
2+
3+
Functional programming helpers for Go — **typed pipelines** built around `Option[T]`.
4+
5+
Import:
6+
7+
```go
8+
import λ "github.com/4thel00z/lambda/v2"
9+
```
10+
11+
## Design notes (v2)
12+
13+
- `Option[T]` holds a value + error.
14+
- Go does **not** allow methods with their own type parameters, and does **not** allow “specialized methods” on `Option[ConcreteType]`.
15+
- Type-changing operations are generic **functions** like `λ.Map`, `λ.Then`, `λ.Try`.
16+
- Type-specific fluent pipelines are implemented via small wrapper types like `λ.Bytes`, `λ.Str`, `λ.Req`, `λ.Resp`, `λ.RSAKeys`.
17+
18+
## Quickstart
19+
20+
### Read all stdin
21+
22+
```go
23+
package main
24+
25+
import (
26+
"os"
27+
λ "github.com/4thel00z/lambda/v2"
28+
)
29+
30+
func main() {
31+
content := λ.Slurp(os.Stdin).String().Must()
32+
_ = content
33+
}
34+
```
35+
36+
### Read a file and write to stdout
37+
38+
```go
39+
package main
40+
41+
import (
42+
"os"
43+
λ "github.com/4thel00z/lambda/v2"
44+
)
45+
46+
func main() {
47+
λ.Open("lorem_ipsum.txt").Slurp().WriteToWriter(os.Stdout)
48+
}
49+
```
50+
51+
### Read JSON into a struct
52+
53+
```go
54+
package main
55+
56+
import (
57+
"fmt"
58+
λ "github.com/4thel00z/lambda/v2"
59+
)
60+
61+
type MagicSpell struct {
62+
Name string `json:"name"`
63+
Power int `json:"power"`
64+
}
65+
66+
func main() {
67+
spell := λ.FromJSON[MagicSpell](λ.Open("magic.json").Slurp()).Must()
68+
fmt.Println(spell.Name, spell.Power)
69+
}
70+
```
71+
72+
### Simple HTTP request
73+
74+
```go
75+
package main
76+
77+
import (
78+
"context"
79+
"os"
80+
λ "github.com/4thel00z/lambda/v2"
81+
)
82+
83+
func main() {
84+
λ.Get("https://example.com").
85+
Do(context.Background()).
86+
Slurp().
87+
WriteToWriter(os.Stdout)
88+
}
89+
```
90+
91+
### Conditionals (kept, typed)
92+
93+
```go
94+
package main
95+
96+
import (
97+
"errors"
98+
λ "github.com/4thel00z/lambda/v2"
99+
)
100+
101+
func main() {
102+
manipulateError := λ.Return(λ.Err[int](errors.New("this error will be thrown")))
103+
input := λ.Wrap(0, errors.New("something is weird"))
104+
output := λ.If(λ.HasError[int], manipulateError).Else(λ.Identity[int]).Do(input)
105+
_ = output
106+
}
107+
```
108+
109+

v2/bytes_string.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package v2
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"io"
7+
"strings"
8+
)
9+
10+
// Bytes is a pipeline wrapper around Option[[]byte].
11+
type Bytes struct{ Option[[]byte] }
12+
13+
// Str is a pipeline wrapper around Option[string].
14+
type Str struct{ Option[string] }
15+
16+
// Lines is a pipeline wrapper around Option[[]string].
17+
type Lines struct{ Option[[]string] }
18+
19+
// Reader is a pipeline wrapper around Option[io.Reader].
20+
type Reader struct{ Option[io.Reader] }
21+
22+
// ReadCloser is a pipeline wrapper around Option[io.ReadCloser].
23+
type ReadCloser struct{ Option[io.ReadCloser] }
24+
25+
// WriteCloser is a pipeline wrapper around Option[io.WriteCloser].
26+
type WriteCloser struct{ Option[io.WriteCloser] }
27+
28+
// BytesOf wraps a raw []byte into a Bytes pipeline.
29+
func BytesOf(v []byte) Bytes { return Bytes{Ok(v)} }
30+
31+
// StrOf wraps a raw string into a Str pipeline.
32+
func StrOf(v string) Str { return Str{Ok(v)} }
33+
34+
// LinesOf wraps a raw []string into a Lines pipeline.
35+
func LinesOf(v []string) Lines { return Lines{Ok(v)} }
36+
37+
// String converts bytes to string.
38+
func (b Bytes) String() Str {
39+
if b.err != nil {
40+
return Str{Err[string](b.err)}
41+
}
42+
return Str{Ok(string(b.v))}
43+
}
44+
45+
// Bytes converts string to bytes.
46+
func (s Str) Bytes() Bytes {
47+
if s.err != nil {
48+
return Bytes{Err[[]byte](s.err)}
49+
}
50+
return Bytes{Ok([]byte(s.v))}
51+
}
52+
53+
// Reader converts bytes to an io.Reader.
54+
func (b Bytes) Reader() Reader {
55+
if b.err != nil {
56+
return Reader{Err[io.Reader](b.err)}
57+
}
58+
var r io.Reader = bytes.NewReader(b.v)
59+
return Reader{Ok(r)}
60+
}
61+
62+
// Reader converts string to an io.Reader.
63+
func (s Str) Reader() Reader {
64+
if s.err != nil {
65+
return Reader{Err[io.Reader](s.err)}
66+
}
67+
var r io.Reader = strings.NewReader(s.v)
68+
return Reader{Ok(r)}
69+
}
70+
71+
// ReadAll reads all content from the contained io.Reader.
72+
func (r Reader) ReadAll() Bytes {
73+
if r.err != nil {
74+
return Bytes{Err[[]byte](r.err)}
75+
}
76+
return ReadAll(r.v)
77+
}
78+
79+
// Slurp reads all content from the contained io.ReadCloser and closes it.
80+
func (r ReadCloser) Slurp() Bytes {
81+
if r.err != nil {
82+
return Bytes{Err[[]byte](r.err)}
83+
}
84+
return Slurp(r.v)
85+
}
86+
87+
// WriteTo implements io.WriterTo for Bytes.
88+
func (b Bytes) WriteTo(w io.Writer) (int64, error) {
89+
if b.err != nil {
90+
return 0, b.err
91+
}
92+
if w == nil {
93+
return 0, errors.New("lambda/v2: nil writer")
94+
}
95+
n, err := w.Write(b.v)
96+
return int64(n), err
97+
}
98+
99+
// WriteToWriter preserves chain-friendly behavior.
100+
func (b Bytes) WriteToWriter(w io.Writer) Bytes {
101+
_, err := b.WriteTo(w)
102+
return Bytes{Wrap(b.v, err)}
103+
}

v2/bytes_string_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package v2
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestBytesStringAndWriteTo(t *testing.T) {
9+
var buf bytes.Buffer
10+
11+
o := BytesOf([]byte("hi"))
12+
if o.String().Must() != "hi" {
13+
t.Fatalf("String mismatch")
14+
}
15+
if string(StrOf("hi").Bytes().Must()) != "hi" {
16+
t.Fatalf("Bytes mismatch")
17+
}
18+
n, err := o.WriteTo(&buf)
19+
if err != nil || n != 2 || buf.String() != "hi" {
20+
t.Fatalf("WriteTo mismatch: n=%d err=%v buf=%q", n, err, buf.String())
21+
}
22+
}

0 commit comments

Comments
 (0)