├── .gitignore ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── examples.cr ├── shard.yml ├── spec ├── adt_spec.cr ├── chain_spec.cr ├── nilable_spec.cr ├── option_spec.cr ├── prelude_spec.cr ├── result_spec.cr └── spec_helper.cr ├── src ├── crz.cr └── crz │ ├── adt.cr │ ├── chain.cr │ ├── interfaces.cr │ ├── lift_apply.cr │ ├── mdo.cr │ ├── nilable.cr │ ├── option.cr │ ├── prelude.cr │ └── result.cr └── watch_test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /.crystal/ 2 | /.shards/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | crystal: 3 | - latest 4 | - nightly 5 | matrix: 6 | allow_failures: 7 | - crystal: nightly -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.tabSize": 2, 4 | "editor.insertSpaces": true 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 dhruvrajvanshi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRZ [![Build Status](https://travis-ci.org/dhruvrajvanshi/crz.svg?branch=master)](https://travis-ci.org/dhruvrajvanshi/crz) 2 | CRZ is a functional programming library for the Crystal language. 3 | 4 | ## Features 5 | * Common monads 6 | - Option 7 | - Nilable 8 | - Result 9 | * Algebraic data types (using macros). 10 | * Automatic generation of `==` and `copy_with` methods like builtin `records` macro. 11 | * Haskell like do notation (more macro goodness). 12 | * Macros for Applicative types. 13 | * Pattern matching 14 | 15 | ## Goals 16 | * Make working with monads/applicatives/functors as pleasant as possible (using macros if needed). 17 | * Enable type safe error handling in the language (using Result(A, E) type). 18 | * Emulate algebraic data types using macros. 19 | * Make working with algebraic types type safe and easy using pattern matching. 20 | 21 | ## Changelog 22 | ### 1.0.0 23 | Breaking changes: 24 | - `==` method is now overridden for adt variants meaning that it will now use value equality instead of reference equality. i.e. 25 | `Option::Success.new(1) == Option::Success.new(1)` will always be true. 26 | - `adt_class` macro has been removed. Instead, you 27 | can simply pass a block containing your custom methods to the `adt` macro. 28 | - `match` macro doesn't need an explicit type argument. It won't work 29 | with the argument anymore. 30 | 31 | ## Quickstart 32 | Add this to your shard.yml 33 | ```yaml 34 | crz: 35 | github: dhruvrajvanshi/crz 36 | version: ~> 1.0.0 37 | ``` 38 | 39 | Then run `shard install` in your project directory. 40 | 41 | ```crystal 42 | include CRZ 43 | ``` 44 | ### Algebraic data types 45 | Algebraic data types are a lightweight way of defining data types 46 | that can be one of multiple sub types, each having its own data values. 47 | Think of them as a single abstract base class with multiple subclasses. 48 | 49 | CRZ provides macros for creating algebraic types with overloaded equality (`==`) 50 | and `to_s` (TODO) methods. 51 | 52 | Define basic algebraic type using adt 53 | ```crystal 54 | ## A list type for integers 55 | adt IntList, # name of tye new type 56 | Empty, 57 | Cons(Int32, IntList) 58 | 59 | ``` 60 | 61 | This declares a type Int list, which can either be an empty list 62 | (subtype IntList::Empty), or an IntList::Cons which contains a head 63 | element (Int32 type) and a tail element which is another IntList. 64 | ```crystal 65 | # Creating adt values 66 | empty = IntList::Empty.new 67 | listWithJust1 = IntList::Cons.new 1, empty 68 | listWith0And1 = IntList::Cons.new 0, (IntList::Cons.new 1, IntList::Empty.new) 69 | ## or 70 | listWith0And1 = IntList::Cons.new 0, listWithJust1 71 | ``` 72 | 73 | #### Named fields 74 | ```crystal 75 | adt Point, 76 | Named { x : Int32, y : Int32 }, 77 | # property x : Int 78 | # property y : Int 79 | 80 | PartiallyNamed { x: Int32, Int32 }, 81 | # property x : Int32 82 | # property value1 : Int32 83 | 84 | Unnamed { Int32, Int32 } 85 | # property value0 : Int32 86 | # property value1 : Int32 87 | ``` 88 | 89 | In case no name is provided, the name of the property will be 90 | `@valueN`, where `N` is the index of the field for that constructor 91 | 92 | #### Accesing values of ADT variants 93 | Each ADT variant (subtype) has instance variables @value0, @value1, 94 | etc according to their index in the data type. 95 | ```crystal 96 | head = listWith0And1.value0 97 | ``` 98 | This method is there but does not utilize the full power of CRZ ADTs. 99 | 100 | #### Cloning and copying 101 | Each variant has a `clone` method that makes a copy of that object. 102 | 103 | `copy_with` method is like clone but fields can be updated individually. 104 | 105 | ```crystal 106 | adt Point, P {x : Int32, y : Int32} 107 | 108 | Point::P.new(1, 2).copy_with(3, 4) # => Point::P(3, 4) 109 | 110 | # Or using field label 111 | Point::P.new(1, 2).copy_with(y: 3) # => Point::P(1, 3) 112 | 113 | ``` 114 | If you don't pass a field to `copy_with`, the one from the current 115 | object is used as a default value. i.e. `copy_with` without any arguments 116 | works like `clone`. 117 | 118 | 119 | #### Pattern matching 120 | All user defined ADTs allow getting values from them using pattern matching. You can write cases corresponding to each variant in the data type and conditionally perform actions. 121 | Example 122 | ```crystal 123 | head = IntList.match listWithJust1, { 124 | [Cons, x, xs] => x, 125 | [Empty] => nil 126 | } 127 | puts head # => 1 128 | ``` 129 | Notice the comma after the variant name (Cons,). This is required. 130 | 131 | You can use [_] pattern as a catch all pattern. 132 | 133 | ```crystal 134 | head = IntList.match empty, { 135 | [Cons, x, xs] => x, 136 | [_] => nil 137 | } 138 | ``` 139 | Note that ordering of patterns matters. For example, 140 | ```crystal 141 | IntList.match list, { 142 | [_] => nil, 143 | [Cons, x, xs] => x, 144 | [Empty] => 0 145 | } 146 | ``` 147 | This will always return nil because ```[_]``` matches everything. 148 | 149 | 150 | You can also use constants in patterns. For example 151 | ```crystal 152 | has0AsHead = IntList.match list, { 153 | [Cons, 0, _] => true, 154 | [_] => false 155 | } 156 | ``` 157 | 158 | You can write statements inside match branches ising Proc literals. 159 | ```crystal 160 | IntList.match list, { 161 | [Empty] => ->{ 162 | print "here" 163 | ... 164 | }.call 165 | } 166 | ``` 167 | You have to add .call at the end of the proc otherwise, it will be returned as a value instead of being called. 168 | 169 | For values with named fields, using a `case` expression is somewhat cleaner. 170 | 171 | ```crystal 172 | adt X, 173 | A { a : Int32 }, 174 | B { b : String } 175 | 176 | x = X::A.new a: 1 177 | ... 178 | 179 | case x 180 | when X::A 181 | # type of x will be narrowed to X::A at compile time 182 | x.a 183 | else 184 | # Inferred as X::B 185 | x.b 186 | end 187 | ``` 188 | 189 | #### Generic ADTs 190 | You can also declare a generic ADTs. 191 | Here's a version of IntList which can be instantiated for any type. 192 | ```crystal 193 | adt List(A), 194 | Empty, 195 | Cons(A, List(A)) 196 | 197 | empty = List::Empty(Int32).new # Type annotation is required for empty 198 | cons = List::Cons.new 1, empty # type annotation isn't required because it is inferred from the first argument 199 | head = List.match cons, { 200 | [Cons, x, _] => x, 201 | [_] => nil 202 | } 203 | ``` 204 | 205 | #### Adding custom methods 206 | You may need to add methods to your ADTs. This can be done by passing a block to the `adt` macro. 207 | For example, here's a partial implementation of `CRZ::Containers::Option` with a few members excluded for brevity. 208 | ```crystal 209 | adt Option(A), 210 | Some(A), 211 | None, 212 | do 213 | include Monad(A) 214 | 215 | def to_s 216 | Option.match self, { 217 | [Some, x] => "Some(#{x})", 218 | [None] => "None", 219 | } 220 | end 221 | 222 | def bind(&block : A -> Option(B)) : Option(B) forall B 223 | Option.match self, { 224 | [Some, x] => (block.call x), 225 | [None] => None(B).new, 226 | } 227 | end 228 | ... 229 | end 230 | ``` 231 | Now all Option values have bind and to_s methods defined on them. 232 | ```crystal 233 | puts Some.new(1).to_s # => Some(1) 234 | puts None(Int32).new.to_s # => None 235 | ``` 236 | Notice that the class has to be abstract and the class name has to be 237 | ADT followed by the name of the type you're declaring otherwise, it won't work. 238 | 239 | ### Container types (Monads) 240 | CRZ defines a few container types which can be used. All of them implement the Monad interface which gives them certain properties that make them really powerful. 241 | One of them is `CRZ::Option` which can either contain a value or nothing. 242 | ```crystal 243 | # Creating an option 244 | a = Option::Some.new 1 245 | none = Option::None(Int32).new 246 | 247 | # you can omit base class name due to type aliases 248 | # defined in CRZ namespace 249 | a = Some.new 2 250 | b = None(Int32).new 251 | 252 | # pattern matching over Option 253 | Option.match a, { 254 | [Some, x] => "Some(#{x})", 255 | [_] => "None" 256 | } # ==> Some(1) 257 | ``` 258 | The idea of the optional type is that whichever functions or methods that can only return a value in some cases should return an Option(A). 259 | The Option type allows you to write clean code without unnecessary nil checks. 260 | 261 | You can transform Options using the .map method 262 | ```crystal 263 | option = Some.new(1) # Some(1) 264 | .map {|x| x+1} # Some(2) 265 | .map {|x| x.to_s} # Some("2") 266 | .map {|s| "asdf" + s} # Some("asdf2") 267 | puts option.to_s # ==> Some(asdf2) 268 | ``` 269 | This allows you to take functions that work on the contained type and apply them to the container. Mapping over Option::None returns an Option::None. 270 | ```crystal 271 | None(Int32).new 272 | .map {|x| x.to_s} # None(String) 273 | ``` 274 | Notice that mapping changes the type of the Option from Option(Int32) to Option(String). 275 | 276 | The .bind method is a bit more powerful than the map method. It allows you to sequence computations that return Option (or any Monad). 277 | Instead of a block of type `A -> B` like map, the bind method takes a block from `A -> Option(B)` and returns Option(B). 278 | For example 279 | ```crystal 280 | Some.new(1) 281 | .bind do |x| 282 | if x == 0 283 | None(Int32).new 284 | else 285 | Some.new(x) 286 | end 287 | end 288 | ``` 289 | The bind is more powerful than you might think. It allows you to combine arbitrary Monads into a single Monad. 290 | 291 | ### Sequencing with mdo macro 292 | What if you have multiple Option types and you want to apply some 293 | computation to their contents without having to manually unwrap their 294 | contents using pattern matching?. There's a way to operate over monads 295 | using normal functions and expressions. 296 | You can do that using mdo macro inspired by Haskell's do notation. 297 | ```crystal 298 | c = mdo({ 299 | x <= Some.new(1), 300 | y <= Some.new(2), 301 | Some.new(x + y) 302 | }) 303 | puts c # ==> Some(3) 304 | ``` 305 | Here, <= isn't the comparison operator. It's job is to bind the 306 | value contained in the monad on it's RHS to the variable on it's left. 307 | Think of it as an assignment for monads. Make sure that the RHS value 308 | for <= inside a mdo block is a monad. Any assignments made like this 309 | can be used in the rest of the mdo body. 310 | You can also use regular assignments in the mdo block to assign regular values. 311 | ```crystal 312 | c = mdo({ 313 | x <= some_option, 314 | ... 315 | y <= another_option, 316 | a = x+y, 317 | ... 318 | Some.new(a) 319 | }) 320 | ``` 321 | If an Option::None is bound anywhere in the mdo body, it short 322 | circuits the entire block and returns a Nothing. The contained type of the nothing will still be 323 | the contained type of the last expression in the block. 324 | ```crystal 325 | c = mdo({ 326 | x <= some_option, 327 | ... 328 | y <= none_option, 329 | ... 330 | ... 331 | }) 332 | puts c.to_s # ==> None 333 | ``` 334 | 335 | Think of what you'd have to do to achieve this result without using mdo or bind. 336 | Instead of this, 337 | ```crystal 338 | # instead of this 339 | c = mdo({ 340 | x <= a, 341 | y <= b, 342 | Some.new(x + y) 343 | }) 344 | ``` 345 | You'd have to write this 346 | ```crystal 347 | Option.match a, { 348 | [Some, x] => Option.match b, { 349 | [Some, y] => Some.new(x+y), 350 | [None] => None(Int32).new 351 | }, 352 | [None] => None(Int32).new 353 | } 354 | ``` 355 | This is harder to read and doesn't scale well to more variables. If you have 10 356 | Option values, you'd have to nest 10 pattern matches. 357 | If you used regular nillable values that the language provides, then it would 358 | turn into nested nil checks which is the same thing. 359 | 360 | Always have a monadic value as the last expression of the mdo block. If you don't, 361 | the return type of mdo block will be (A | None(A)). 362 | 363 | Remember when I said .bind method is really powerful? An mdo block is transformed 364 | into nested binds during macro expansion. 365 | 366 | There's an even cleaner way to write combination of monads. 367 | 368 | ### lift_apply macro 369 | Suppose you have a function like 370 | ```crystal 371 | def sum(x, y) 372 | x + y 373 | end 374 | ``` 375 | and you want to apply this function to two monads instead of two values. 376 | You can use an mdo block but an even cleaner way is to write 377 | ``` 378 | lift_apply sum, Some.new(1), Some.new(2) 379 | ``` 380 | You can also use a proc 381 | ``` 382 | lift_apply proc.call, monad1, monad2, ... 383 | ``` 384 | Just like mdo, this is also converted into nested .bind calls during macro expansion. 385 | 386 | It is advisable to keep your values inside monads for as long as possible and match 387 | over them at the end. You already know how to use regular functions over monadic values. 388 | 389 | ### Other operators on monads 390 | All monads implement these methods 391 | * .of 392 | * ```>>``` 393 | * ```<<``` 394 | 395 | To create a monad from a single value, use the .of method 396 | ```crystal 397 | Option.of(2) # => Some(2) 398 | Result(Int32, String).of(2) # => Ok(2) 399 | ``` 400 | 401 | To sequence two monads, discarding the value of the first monad, use the operator ```>>``` 402 | ```crystal 403 | Option.of(2) >> Option.of(3) # => Some(3) 404 | None(Int32).new >> Option.of(3) # => None 405 | Option.of(2) >> None(Int32).new # => None 406 | ``` 407 | 408 | To sequence two monads, discarding the value of the second 409 | monad, use the ```<<``` operator. 410 | ```crystal 411 | Option.of(2) << Option.of(3) # => Some(2) 412 | ``` 413 | 414 | ### Implementing your own monads 415 | To implement your own monadic types, you have to include the Monad(T) module in your class, and you have to implement 416 | the .of, bind and map methods (you can omit the map method if 417 | your monad takes only one generic type argument). of method 418 | is a static method, so, it is named self.of. 419 | For example, Option type is defined as 420 | ```crystal 421 | adt_class Option(A), 422 | Some(A), None, 423 | abstract class ADTOption(A) 424 | include Monad(A) 425 | 426 | def self.of(value : T) : Option(T) forall T 427 | Option::Some.new(value) 428 | end 429 | 430 | def bind(&block : A -> Option(B)) : Option(B) forall B 431 | Option.match self, { 432 | [Some, x] => (block.call x), 433 | [None] => Option::None(B).new, 434 | } 435 | end 436 | end 437 | ``` 438 | In case your type requires more than 1 generic argument, you 439 | can implement the map method in a straightforward way using 440 | the bind and of methods. 441 | ```crystal 442 | class YourType(A1, A2) 443 | include Monad(A1) 444 | 445 | ... 446 | 447 | def map(&block : A1 -> B) : YourType(B, A2) forall B 448 | bind do |x| 449 | YourType(B, A2).of(block.call x) 450 | end 451 | end 452 | end 453 | 454 | # or, in case your monad is based on the second generic arg, 455 | class YourType(A1, A2) 456 | include Monad(A2) 457 | 458 | ... 459 | 460 | def map(&block : A2 -> B) : YourType(A1, B) forall B 461 | bind do |x| 462 | YourType(A1, B).of(block.call x) 463 | end 464 | end 465 | end 466 | ``` 467 | Any monads you define will be compatible with mdo and 468 | lift_apply macros. 469 | -------------------------------------------------------------------------------- /examples.cr: -------------------------------------------------------------------------------- 1 | require "./src/crz" 2 | include CRZ 3 | 4 | # # Basic algebraic data type 5 | adt IntList, 6 | Empty, 7 | Cons(Int32, IntList) 8 | 9 | empty = IntList::Empty.new 10 | listWithJust1 = IntList::Cons.new 1, empty 11 | listWith0And1 = IntList::Cons.new 0, (IntList::Cons.new 1, IntList::Empty.new) 12 | ## or 13 | listWith0And1 = IntList::Cons.new 0, listWithJust1 14 | 15 | pp empty 16 | pp listWithJust1 17 | pp listWith0And1 18 | 19 | head = IntList.match listWithJust1, IntList, { 20 | [Cons, x, xs] => x, 21 | [Empty] => nil 22 | } 23 | 24 | pp head 25 | 26 | 27 | adt List(A), 28 | Empty, 29 | Cons(A, List(A)) 30 | 31 | empty = List::Empty(Int32).new 32 | cons = List::Cons.new 1, empty 33 | head = List.match cons, List(Int32), { # Just List won't work here, it has to be concrete type List(Int32) 34 | [Cons, x, _] => x, 35 | [_] => nil 36 | } 37 | 38 | pp head 39 | option = Option::Some.new(1) 40 | .map {|x| x+1} 41 | .map {|x| x.to_s} 42 | .map {|s| "asdf" + s} 43 | puts option.to_s 44 | 45 | def sum(x, y) 46 | x + y 47 | end 48 | 49 | a = lift_apply sum, Option::Some.new(1), Option::Some.new(2) 50 | puts a.to_s 51 | 52 | c = mdo({ 53 | x <= Option::Some.new(1), 54 | y <= Option::Some.new(2), 55 | Option::Some.new(x + y) 56 | }) 57 | puts c.to_s # ==> Some(3) -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: crz 2 | version: 1.0.2 3 | 4 | license: MIT 5 | 6 | crystal: ">= 0.35.0, < 2.0.0" 7 | 8 | author: 9 | - Dhruv Rajvanshi -------------------------------------------------------------------------------- /spec/adt_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "./spec_helper" 3 | # include CRZ 4 | 5 | adt IntList, 6 | Empty, 7 | Cons(Int32, IntList) 8 | 9 | 10 | adt IntResult, 11 | Error, 12 | Success(Int32) 13 | 14 | adt Res(T, E), 15 | Error(E), 16 | Success(T), 17 | Empty 18 | 19 | adt Res(T, E), 20 | Error(E), 21 | Success(T), 22 | Empty 23 | 24 | 25 | adt Pair(A, B), Pair(A, B) 26 | 27 | adt Named, 28 | N {x : Int32, y : Int32} 29 | 30 | adt List(A), 31 | Empty, 32 | Cons(A, List(A)) 33 | describe CRZ do 34 | it "creates constructors for non generic adt" do 35 | empty = IntList::Empty.new 36 | cons = IntList::Cons.new 1, empty 37 | cons.value0.should eq 1 38 | cons.value1.should eq empty 39 | end 40 | 41 | it "works with pattern matching" do 42 | empty = IntList::Empty.new 43 | cons = IntList::Cons.new 1, empty 44 | 45 | v = IntList.match(empty, { 46 | [Empty] => 1, 47 | [_] => 2, 48 | }) 49 | v.should eq 1 50 | 51 | IntList.match(empty, { 52 | [Cons, x, xs] => 2, 53 | [_] => 1, 54 | }).should eq 1 55 | 56 | IntList.match(cons, { 57 | [Cons, x, xs] => x, 58 | [_] => 2, 59 | }).should eq 1 60 | 61 | IntList.match(cons, { 62 | [Cons, 0, xs] => 2, 63 | [_] => 1, 64 | }).should eq 1 65 | 66 | IntList.match(cons, { 67 | [Cons, 0, xs] => 1, 68 | [Cons, 1, xs] => 2, 69 | [_] => 3, 70 | }).should eq 2 71 | end 72 | 73 | it "generates equality for non generic adts" do 74 | r1 = IntResult::Error.new 75 | r2 = IntResult::Error.new 76 | r1.should eq r2 77 | 78 | IntResult::Success.new(1).should eq IntResult::Success.new(1) 79 | (IntResult::Success.new(1) == IntResult::Success.new(2)) 80 | .should eq false 81 | 82 | (IntResult::Success.new(1) == IntResult::Error) 83 | .should eq false 84 | 85 | IntList::Cons.new(1, IntList::Empty.new) 86 | .should eq IntList::Cons.new(1, IntList::Empty.new) 87 | end 88 | 89 | it "generates equality for generic adts" do 90 | r1 = Res::Error(String, String).new "1" 91 | r2 = Res::Error(String, String).new "1" 92 | r1.should eq r2 93 | 94 | r3 = Res::Empty(String, String).new 95 | r4 = Res::Empty(String, String).new 96 | r3.should eq r4 97 | 98 | Res::Empty(String, Int32).new.should eq Res::Empty(String, String).new 99 | 100 | Res::Success(Int32, Int32).new(1).should eq Res::Success(Int32, Int32).new(1) 101 | (Res::Success(Int32, Int32).new(1) == Res::Success(Int32, Int32).new(2)) 102 | .should eq false 103 | end 104 | 105 | it "generates equality for adt classes" do 106 | Option.of(1).should eq Option.of(1) 107 | (Option.of(1) == Option.of(2)).should eq false 108 | Option::None(Int32).new.should eq Option::None(Int32).new 109 | (Option::None(Int32).new == Option::Some.new(1)).should eq false 110 | end 111 | 112 | it "generates clone method" do 113 | Pair::Pair.new(1, 2).clone.should eq Pair::Pair.new(1, 2) 114 | end 115 | 116 | it "generates copy_with method" do 117 | copied = Pair::Pair.new(1, 2).copy_with value1: 4 118 | copied2 = Pair::Pair.new(1, 2).copy_with value0: 3 119 | copied.should eq Pair::Pair.new(1, 4) 120 | copied2.should eq Pair::Pair.new(3, 2) 121 | end 122 | 123 | it "generates named properties" do 124 | n = Named::N.new x: 1, y: 2 125 | n.x.should eq 1 126 | n.y.should eq 2 127 | n.copy_with(x: 3, y: 4).should eq Named::N.new(3, 4) 128 | end 129 | 130 | it "pattern matches generic types" do 131 | empty = List::Empty(Int32).new 132 | cons = List::Cons.new 1, empty 133 | 134 | head = List.match cons, { 135 | [Cons, x, _] => x, 136 | [_] => nil 137 | } 138 | head.should eq 1 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /spec/chain_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "./spec_helper" 3 | 4 | describe "chain macro" do 5 | it "chain works on functions" do 6 | res = chain 1, increment, to_s 7 | res.should eq "2" 8 | end 9 | 10 | it "chain works on procs" do 11 | res = chain 2, ->(x : Int32) {x*2}.call, ->(x : Int32){x+1}.call 12 | res.should eq 5 13 | end 14 | end 15 | 16 | def increment(x) 17 | x + 1 18 | end 19 | 20 | def to_s(x) 21 | x.to_s 22 | end -------------------------------------------------------------------------------- /spec/nilable_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "./spec_helper" 3 | 4 | describe Nilable do 5 | it "implements of method" do 6 | o = Nilable.of(2) 7 | typeof(o).should eq Nilable(Int32) 8 | end 9 | 10 | it "works as a functor" do 11 | some = Nilable.of 34 12 | mapped = some.map do |x| 13 | x + 1 14 | end 15 | 16 | mapped.value.should eq 35 17 | 18 | string_Nilable = mapped.map &.to_s 19 | string_Nilable.value.should eq "35" 20 | 21 | none = Nilable(Int32).new nil 22 | mapped = none.map do |x| 23 | 1.should eq 2 24 | x + 3 25 | end 26 | 27 | mapped.value.should eq nil 28 | end 29 | 30 | it "works as an applicative" do 31 | someF = Nilable.of ->(x : Int32) { 32 | x + 1 33 | } 34 | some12 = Nilable.of(12) 35 | some12.value.should eq 12 36 | 37 | none = Nilable(Int32).new nil 38 | applied = none.ap(someF) 39 | applied.value.should eq nil 40 | 41 | noop = Nilable(Int32 -> Int32).new 42 | some12.ap(noop).value.should eq nil 43 | 44 | f = ->(x : Int32, y : Int32) { 45 | x + y 46 | } 47 | 48 | (lift_apply f.call, some12, Nilable.of(2)).value.should eq 14 49 | (lift_apply sum, some12, Nilable.of(2)).value.should eq 14 50 | 51 | (lift_apply sum, Nilable.of(1), Nilable.of(2), Nilable.of(3)).value.should eq 6 52 | 53 | (lift_apply sum, Nilable.of(1), Nilable(Int32).new, Nilable.of(2)).value.should eq nil 54 | end 55 | 56 | it "works as a monad" do 57 | (Nilable.of(1).bind { |x| Nilable.of(x + 1) }).value.should eq 2 58 | (Nilable(Int32).new.bind { |x| Nilable.of(x + 1) }).value.should eq nil 59 | (Nilable.of(1).bind { |x| Nilable(Int32).new }).value.should eq nil 60 | 61 | mdo({ 62 | x <= Nilable.of(1), 63 | y <= Nilable.of(2), 64 | Nilable.of(x + y), 65 | }).value.should eq 3 66 | 67 | mdo({ 68 | x <= Nilable.of(1), 69 | a = x + 1, 70 | y <= Nilable.of(2), 71 | Nilable.of(a + y), 72 | }).value.should eq 4 73 | mdo({ 74 | x <= Nilable.of(1), 75 | y <= Nilable(Int32).new, 76 | z <= Nilable.of(2), 77 | Nilable.of(x + y + z), 78 | }).value.should eq nil 79 | 80 | mdo({ 81 | x <= Nilable.of(1), 82 | y <= mdo({ 83 | a <= Nilable.of(3), 84 | b <= Nilable.of(3), 85 | c = 4, 86 | Nilable.of(a + b + c), 87 | }), 88 | z <= Nilable.of(2), 89 | Nilable.of(x + y + z), 90 | }).value.should eq 13 91 | end 92 | end 93 | 94 | def sum(*args) 95 | result = 0 96 | args.each { |x| result += x } 97 | result 98 | end 99 | -------------------------------------------------------------------------------- /spec/option_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "./spec_helper" 3 | 4 | describe Option do 5 | it "creates Option::Some from constructor" do 6 | o = Option::Some.new 42 7 | o.value0.should eq 42 8 | typeof(o).should eq Some(Int32) 9 | (o.responds_to? :map).should eq true 10 | (o.responds_to? :bind).should eq true 11 | (o.responds_to? :ap).should eq true 12 | (o.responds_to? :>>).should eq true 13 | (o.responds_to? :<<).should eq true 14 | (o.responds_to? :>=).should eq true 15 | (o.responds_to? :*).should eq true 16 | end 17 | 18 | it "creates Option::None from constructor" do 19 | o = Option::None(Int32).new 20 | typeof(o).should eq None(Int32) 21 | (o.responds_to? :map).should eq true 22 | (o.responds_to? :bind).should eq true 23 | (o.responds_to? :ap).should eq true 24 | (o.responds_to? :>>).should eq true 25 | (o.responds_to? :<<).should eq true 26 | (o.responds_to? :>=).should eq true 27 | (o.responds_to? :*).should eq true 28 | end 29 | 30 | it "implements of method" do 31 | o = Option.of(2) 32 | typeof(o).should eq Some(Int32) 33 | end 34 | 35 | it "works as a functor" do 36 | some = Some.new 34 37 | mapped = some.map do |x| 38 | x.should eq 34 39 | x + 1 40 | end 41 | Option.match mapped, { 42 | [Some, x] => (x.should eq 35), 43 | [_] => (1.should eq 2), 44 | } 45 | 46 | string_option = mapped.map &.to_s 47 | Option.match string_option, { 48 | [Some, x] => (x.should eq "35"), 49 | [_] => (1.should eq 2), 50 | } 51 | 52 | none = Option::None(Int32).new 53 | mapped = none.map do |x| 54 | 1.should eq 2 55 | x + 3 56 | end 57 | 58 | Option.match mapped, { 59 | [Some, x] => (1.should eq 2), 60 | [None] => (1.should eq 1), 61 | } 62 | end 63 | 64 | it "works as an applicative" do 65 | someF = Option.of ->(x : Int32) { 66 | x + 1 67 | } 68 | some12 = Option.of(12) 69 | some12.unwrap.should eq 12 70 | 71 | none = Option::None(Int32).new 72 | applied = none.ap(someF) 73 | applied.has_value.should eq false 74 | 75 | noop = Option::None(Int32 -> Int32).new 76 | some12.ap(noop).has_value.should eq false 77 | 78 | f = ->(x : Int32, y : Int32) { 79 | x + y 80 | } 81 | 82 | (lift_apply f.call, some12, Option.of(2)).unwrap.should eq 14 83 | (lift_apply sum, some12, Option.of(2)).unwrap.should eq 14 84 | 85 | (lift_apply sum, Option.of(1), Option.of(2), Option.of(3)).unwrap.should eq 6 86 | 87 | (lift_apply sum, Option.of(1), Option::None(Int32).new, Option.of(2)).has_value.should eq false 88 | end 89 | 90 | it "works as a monad" do 91 | (Option.of(1).bind { |x| Option.of(x + 1) }).unwrap.should eq 2 92 | (Option::None(Int32).new.bind { |x| Option.of(x + 1) }).has_value.should eq false 93 | (Option.of(1).bind { |x| Option::None(Int32).new }).has_value.should eq false 94 | 95 | mdo({ 96 | x <= Option.of(1), 97 | y <= Option.of(2), 98 | Option.of(x + y), 99 | }).unwrap.should eq 3 100 | 101 | mdo({ 102 | x <= Option.of(1), 103 | a = x + 1, 104 | y <= Option.of(2), 105 | Option.of(a + y), 106 | }).unwrap.should eq 4 107 | mdo({ 108 | x <= Option.of(1), 109 | y <= Option::None(Int32).new, 110 | z <= Option.of(2), 111 | Option.of(x + y + z), 112 | }).has_value.should eq false 113 | 114 | mdo({ 115 | x <= Option.of(1), 116 | y <= mdo({ 117 | a <= Option.of(3), 118 | b <= Option.of(3), 119 | c = 4, 120 | Option.of(a + b + c), 121 | }), 122 | z <= Option.of(2), 123 | Option.of(x + y + z), 124 | }).unwrap.should eq 13 125 | end 126 | 127 | it "implements >> operator" do 128 | o = Option.of(1) >> Option.of(2) 129 | o.unwrap.should eq 2 130 | 131 | o = Option.of(1) >> None(Int32).new 132 | o.class.should eq None(Int32) 133 | 134 | o = None(Int32).new >> Option.of(1) 135 | o.class.should eq None(Int32) 136 | end 137 | 138 | it "implements << operator" do 139 | o = Option.of(1) << Option.of(2) 140 | o.unwrap.should eq 1 141 | 142 | o = Option.of(1) << None(Int32).new 143 | o.class.should eq None(Int32) 144 | 145 | o = None(Int32).new << Option.of(1) 146 | o.class.should eq None(Int32) 147 | end 148 | 149 | it "unwrap_or" do 150 | Option.of(1).unwrap_or_else(0).should eq 1 151 | Option::None(Int32).new.unwrap_or_else(0).should eq 0 152 | end 153 | end 154 | 155 | def sum(*args) 156 | result = 0 157 | args.each { |x| result += x } 158 | result 159 | end 160 | -------------------------------------------------------------------------------- /spec/prelude_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "./spec_helper" 3 | 4 | describe CRZ do 5 | it "has correct implementation of id" do 6 | id(1).should eq 1 7 | id(true).should eq true 8 | id("asdf").should eq "asdf" 9 | end 10 | 11 | it "has correct implementation of map" do 12 | some3 = map ->(x : Int32) { 13 | x.should eq 2 14 | x + 1 15 | }, Option::Some.new(2) 16 | some3.unwrap.should eq 3 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/result_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "./spec_helper" 3 | 4 | describe Result do 5 | it "creates Result::Ok from constructor" do 6 | o = Result::Ok(Int32, String).new 42 7 | o.value0.should eq 42 8 | typeof(o).should eq Result::Ok(Int32, String) 9 | (o.responds_to? :map).should eq true 10 | (o.responds_to? :bind).should eq true 11 | (o.responds_to? :ap).should eq true 12 | (o.responds_to? :>>).should eq true 13 | (o.responds_to? :<<).should eq true 14 | (o.responds_to? :>=).should eq true 15 | (o.responds_to? :*).should eq true 16 | end 17 | 18 | it "creates Result::Err from constructor" do 19 | o = Result::Err(Int32, String).new "Error" 20 | typeof(o).should eq Result::Err(Int32, String) 21 | (o.responds_to? :map).should eq true 22 | (o.responds_to? :bind).should eq true 23 | (o.responds_to? :ap).should eq true 24 | (o.responds_to? :>>).should eq true 25 | (o.responds_to? :<<).should eq true 26 | (o.responds_to? :>=).should eq true 27 | (o.responds_to? :*).should eq true 28 | end 29 | 30 | it "implements of method" do 31 | o = Result(Int32, String).of 2 32 | typeof(o).should eq Result::Ok(Int32, String) 33 | end 34 | 35 | it "works as a functor" do 36 | ok = Result::Ok(Int32, String).new 34 37 | mapped = ok.map do |x| 38 | x.should eq 34 39 | x + 1 40 | end 41 | Result.match mapped, { 42 | [Ok, x] => (x.should eq 35), 43 | [_] => (1.should eq 2), 44 | } 45 | 46 | string_result = mapped.map &.to_s 47 | Result.match string_result, { 48 | [Ok, x] => (x.should eq "35"), 49 | [_] => (1.should eq 2), 50 | } 51 | 52 | err = Result::Err(Int32, String).new "Error" 53 | mapped = err.map do |x| 54 | 1.should eq 2 55 | x + 3 56 | end 57 | 58 | Result.match mapped, { 59 | [Ok, x] => (1.should eq 2), 60 | [Err, e] => (e.should eq "Error"), 61 | } 62 | end 63 | 64 | it "works as an applicative" do 65 | okF = Result::Ok((Int32 -> Int32), String).new ->(x : Int32) { 66 | x + 1 67 | } 68 | ok12 = Result::Ok(Int32, String).new(12) 69 | ok12.unwrap.should eq 12 70 | 71 | none = Result::Err(Int32, String).new "Err" 72 | applied = none.ap(okF) 73 | applied.has_value.should eq false 74 | 75 | noop = Result::Err((Int32 -> Int32), String).new "Err" 76 | ok12.ap(noop).has_value.should eq false 77 | 78 | f = ->(x : Int32, y : Int32) { 79 | x + y 80 | } 81 | 82 | ok1 = Result::Ok(Int32, String).new 1 83 | ok2 = Result::Ok(Int32, String).new 2 84 | ok3 = Result::Ok(Int32, String).new 3 85 | 86 | (lift_apply f.call, ok12, ok2).unwrap.should eq 14 87 | (lift_apply sum, ok12, ok2).unwrap.should eq 14 88 | 89 | (lift_apply sum, ok1, ok2, ok3).unwrap.should eq 6 90 | 91 | (lift_apply sum, ok1, none, ok2).has_value.should eq false 92 | end 93 | 94 | it "works as a monad" do 95 | ok1 = Result::Ok(Int32, String).new(1) 96 | (ok1.bind { |x| Result::Ok(Int32, String).new(x + 1) }).unwrap.should eq 2 97 | (Result::Err(Int32, String).new("Err").bind { |x| Result(Int32, String).of(x + 1) }).has_value.should eq false 98 | (ok1.bind { |x| Result::Err(Int32, String).new "Err" }).has_value.should eq false 99 | 100 | ok2 = Result::Ok(Int32, String).new(2) 101 | ok3 = Result::Ok(Int32, String).new(3) 102 | mdo({ 103 | x <= ok1, 104 | y <= mdo({ 105 | a <= ok3, 106 | b <= ok3, 107 | c = 4, 108 | Result(Int32, String).of(a + b + c), 109 | }), 110 | z <= ok2, 111 | Result(Int32, String).of(x + y + z), 112 | }).unwrap.should eq 13 113 | end 114 | end 115 | 116 | def sum(*args) 117 | result = 0 118 | args.each { |x| result += x } 119 | result 120 | end 121 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "../src/crz" 2 | -------------------------------------------------------------------------------- /src/crz.cr: -------------------------------------------------------------------------------- 1 | require "./crz/*" 2 | 3 | module CRZ 4 | include CRZ::Containers 5 | include CRZ::Monad::Macros 6 | include CRZ::Prelude 7 | end 8 | -------------------------------------------------------------------------------- /src/crz/adt.cr: -------------------------------------------------------------------------------- 1 | module CRZ 2 | 3 | macro adt(base_type, *args) 4 | {% if base_type.class_name == "Path" %} 5 | {% 6 | base_class = base_type.names[0] 7 | is_generic = false 8 | generics = [] of ArrayLiteral 9 | %} 10 | {% else %} 11 | {% 12 | base_class = base_type.name.names[0] 13 | is_generic = true 14 | generics = base_type.type_vars 15 | %} 16 | {% end %} 17 | 18 | # base class 19 | abstract class {{base_type}} 20 | {{ yield }} 21 | {% for i in 0...args.size %} 22 | {% if args[i].class_name == "Path" %} 23 | # case with no fields 24 | {% 25 | subclass_name = args[i].names[0] 26 | members = [] of ArrayLiteral 27 | %} 28 | {% elsif args[i].is_a?(ArrayLiteral) %} 29 | {% 30 | subclass_name = args[i].stringify.split('{')[0].strip.id 31 | members = args[i].map do |m| 32 | if m.is_a?(Path) 33 | m.id 34 | else 35 | m.type 36 | end 37 | end 38 | %} 39 | {% else %} 40 | # case with fields 41 | {% 42 | subclass_name = args[i].name 43 | members = args[i].type_vars 44 | %} 45 | {% end %} 46 | 47 | {% 48 | member_names = [] of String 49 | %} 50 | {% if args[i].is_a?(ArrayLiteral) %} 51 | {% for j in 0...args[i].size %} 52 | {% if args[i][j].is_a?(TypeDeclaration) %} 53 | # {{ member_names << args[i][j].var }} 54 | {% else %} 55 | # {{ member_names << "value#{j}".id }} 56 | {% end %} 57 | {% end %} 58 | {% else %} 59 | {% for j in 0...members.size %} 60 | # next line has to be commented because 61 | # << method returns the array causing it to 62 | # be included in the generated class. Commenting it 63 | # out is a simple hack to prevent it from happening 64 | # {{ member_names << "value#{j}".id }} 65 | {% end %} 66 | {% end %} 67 | 68 | {% if is_generic %} 69 | {% generic_param_list = "(#{generics.join(", ").id})".id %} 70 | {% else %} 71 | {% generic_param_list = "".id %} 72 | {% end %} 73 | 74 | class {{subclass_name}}{{generic_param_list}} < {{base_type}} 75 | {% for j in 0...members.size %} 76 | property {{member_names[j]}} : {{members[j]}} 77 | {% end %} 78 | def initialize( 79 | {% for j in 0...members.size %} 80 | {{"@#{member_names[j]}".id}} : {{members[j]}}, 81 | {% end %} 82 | ) 83 | end 84 | 85 | def ==(other : Other) forall Other 86 | case other 87 | when {{subclass_name}} 88 | {% for arg_i in 0...member_names.size %} 89 | return false if @{{member_names[arg_i]}} != other.{{member_names[arg_i]}} 90 | {% end %} 91 | return true 92 | else 93 | false 94 | end 95 | end 96 | 97 | def clone : {{subclass_name}}{{generic_param_list}} 98 | {{subclass_name}}{{generic_param_list}}.new( 99 | {{*member_names.map {|m| "@#{m}".id}}} 100 | ) 101 | end 102 | 103 | def copy_with({{ 104 | *member_names.map do |member| 105 | "#{member} = @#{member}".id 106 | end 107 | }}) : {{subclass_name}}{{generic_param_list}} 108 | {{subclass_name}}{{generic_param_list}}.new({{ 109 | *member_names 110 | }}) 111 | end 112 | end 113 | {% end %} 114 | 115 | macro match(val, cases) 116 | \{% if cases.class_name != "HashLiteral" %} 117 | \{{cases.raise "SyntaxError: " + "Second argument to match " + 118 | "should be a HashLiteral containing patterns" 119 | }} 120 | \{% end %} 121 | -> { 122 | %matcher_func = -> { 123 | \{% literal_classes = ["NumberLiteral", "StringLiteral", "BoolLiteral", "NilLiteral", "CharLiteral", "SymbolLiteral"] %} 124 | %value = \{{val}} 125 | case %value 126 | {% for i in 0...args.size %} 127 | {% derived = args[i] %} 128 | {% if derived.is_a?(Path) %} 129 | {% derived_name = derived.names[0] %} 130 | {% elsif derived.is_a?(ArrayLiteral) %} 131 | {% derived_name = derived[0].var %} 132 | {% else %} 133 | {% derived_name = derived.name %} 134 | {% end %} 135 | when {{base_class}}::{{derived_name}} 136 | \{% for pattern in cases.keys %} 137 | \{% expression = cases[pattern] %} 138 | 139 | \{% if pattern[0].class_name == "Path" %} 140 | \{% if pattern[0].names[0].stringify == {{derived_name.stringify}} %} 141 | \{% literals = pattern.select {|p| literal_classes.includes? p.class_name } %} 142 | \{% for literal, j in pattern %} 143 | \{% if literal_classes.includes? literal.class_name %} 144 | if(%value.responds_to?(:value\{{j-1}}) && %value.value\{{j-1}} == \{{literal}}) 145 | \{% end %} 146 | \{% end %} 147 | \{% for j in 1...pattern.size %} 148 | \{% if pattern[j].class_name == "Var" %} 149 | \{{pattern[j]}} = %value.value\{{j - 1}} 150 | \{% elsif pattern[j].class_name == "Call" %} 151 | \{{pattern[j].id}} = %value.value\{{j - 1}} 152 | \{% end %} 153 | \{% end %} 154 | return \{{expression}} 155 | \{% for literal in literals%} 156 | end 157 | \{% end %} 158 | \{% end %} 159 | \{% elsif pattern[0].class_name == "Underscore" %} 160 | return \{{expression}} 161 | \{% else %} 162 | \{{pattern[0].raise "Syntax error: " + pattern[0].class_name + " not allowed here."}} 163 | \{% end %} 164 | \{% end %} 165 | raise "Non exhaustive patterns" 166 | {% end %} 167 | 168 | end 169 | raise "Non exhaustive patterns" 170 | } 171 | %matcher_func.call() 172 | }.call 173 | end 174 | end 175 | end 176 | end 177 | -------------------------------------------------------------------------------- /src/crz/chain.cr: -------------------------------------------------------------------------------- 1 | module CRZ::Prelude 2 | macro chain(arg, *funcs) 3 | {% for func, index in funcs %} 4 | {% i = funcs.size - index - 1%} 5 | {{funcs[i]}}( 6 | {% end %} 7 | {{arg}} 8 | {% for func in funcs %} 9 | ) 10 | {% end %} 11 | end 12 | end -------------------------------------------------------------------------------- /src/crz/interfaces.cr: -------------------------------------------------------------------------------- 1 | module CRZ 2 | module Functor(A) 3 | 4 | def replace(other : Functor(B), v : A) : Functor(A) 5 | map { |_| v } 6 | end 7 | end 8 | 9 | module Applicative(A) 10 | include Functor(A) 11 | 12 | def *(func : Applicative(A -> B)) : Applicative(B) forall B 13 | ap(func) 14 | end 15 | 16 | def self.of(value : A) : Applicative(A) forall A 17 | raise "of method unimplemented" 18 | end 19 | end 20 | 21 | module Monad(A) 22 | include Applicative(A) 23 | 24 | 25 | def ap(func : Applicative(A -> B)) : Applicative(B) forall B 26 | func.bind do |f| 27 | self.map &f 28 | end 29 | end 30 | 31 | def map(&block : A -> B) : Monad(B) forall B 32 | bind do |x| 33 | typeof(self).of(block.call x) 34 | end 35 | end 36 | 37 | def >=(block : A -> Monad(B)) : Monad(B) forall B 38 | bind do |x| 39 | block.call(x) 40 | end 41 | end 42 | 43 | def >>(other : Monad(B)) : Monad(B) forall B 44 | bind { |_| other } 45 | end 46 | 47 | def <<(other : Monad(B)) : Monad(A) forall B 48 | bind { |v| 49 | other.map {|_| 50 | v 51 | } 52 | } 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /src/crz/lift_apply.cr: -------------------------------------------------------------------------------- 1 | module CRZ::Monad::Macros 2 | macro lift_apply(f, *args) 3 | {% for i in 0...args.size %} 4 | {{args[i]}}.bind { |arg{{i}}| 5 | {% end %} 6 | 7 | typeof({{args[0]}}).of( 8 | {{f.id}}( 9 | {% for i in 0...args.size - 1 %} 10 | arg{{i}}, 11 | {% end %} 12 | arg{{args.size - 1}} 13 | ) 14 | ) 15 | 16 | {% for i in 0...args.size %} 17 | } 18 | {% end %} 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /src/crz/mdo.cr: -------------------------------------------------------------------------------- 1 | module CRZ::Monad::Macros 2 | macro mdo(body) 3 | {% if ["Assign", "TypeNode", "Splat", "Union", "UninitializedVar", "TypeDeclaration", 4 | "Generic", "ClassDef", "Def", "VisibilityModifier", "MultiAssign"].includes? body[body.size - 1].class_name %} 5 | {{body[body.size-1].raise "Last line of an mdo expression should be an expression."}} 6 | {% end %} 7 | {{body[0].args[0]}}.bind do |{{body[0].receiver}}| # 0 8 | {% for i in 1...body.size - 1 %} 9 | {% if body[i].class_name == "Assign" %} 10 | {{body[i].target}} = {{body[i].value}} 11 | {% else %} 12 | {% if body[i].class_name == "Call" && body[i].name == "<=" %} 13 | {{body[i].args[0]}}.bind do |{{body[i].receiver}}| # {{i}} 14 | {% else %} 15 | {{body[i].raise "Only <= or = are allowed"}} 16 | {% end %} 17 | {% end %} 18 | {% end %} 19 | {{body[body.size - 1]}} 20 | 21 | # place end for all monad assignments in body 22 | {% for i in 1...body.size - 1 %} 23 | {% if body[i].class_name == "Call" %} 24 | end 25 | {% end %} 26 | {% end %} 27 | end # end body[0].bind 28 | end 29 | end -------------------------------------------------------------------------------- /src/crz/nilable.cr: -------------------------------------------------------------------------------- 1 | module CRZ::Containers 2 | property value 3 | 4 | class Nilable(A) 5 | include Monad(A) 6 | 7 | property value 8 | 9 | def initialize(@value : A? = nil) 10 | end 11 | 12 | def self.of(value : A) forall A 13 | Nilable.new value 14 | end 15 | 16 | def bind(&block : A -> Nilable(B)) : Nilable(B) forall B 17 | if @value.nil? 18 | Nilable(B).new 19 | else 20 | yield @value.as(A) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /src/crz/option.cr: -------------------------------------------------------------------------------- 1 | include CRZ 2 | 3 | module CRZ::Containers 4 | alias Some = Option::Some 5 | alias None = Option::None 6 | adt Option(A), 7 | Some(A), 8 | None do 9 | include Monad(A) 10 | 11 | def to_s 12 | Option.match self, Option(A), { 13 | [Some, x] => "Some(#{x})", 14 | [None] => "None", 15 | } 16 | end 17 | 18 | def self.of(value : T) : Option(T) forall T 19 | Option::Some.new(value) 20 | end 21 | 22 | def unwrap : A 23 | Option.match self, { 24 | [Some, x] => x, 25 | [None] => raise Exception.new("Tried to unwrap Option::None value"), 26 | } 27 | end 28 | 29 | def get : A 30 | unwrap 31 | end 32 | 33 | def unwrap_or_else(default : A) : A 34 | Option.match self, { 35 | [Some, x] => x, 36 | [None] => default 37 | } 38 | end 39 | 40 | def get_or_else(default : A) : A 41 | unwrap_or_else(default) 42 | end 43 | 44 | def has_value : Bool 45 | Option.match self, { 46 | [Some, _] => true, 47 | [_] => false, 48 | } 49 | end 50 | 51 | def flat_map(&block : A -> Option(B)) : Option(B) forall B 52 | bind(block) 53 | end 54 | 55 | def bind(&block : A -> Option(B)) : Option(B) forall B 56 | Option.match self, { 57 | [Some, x] => (block.call x), 58 | [None] => Option::None(B).new, 59 | } 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /src/crz/prelude.cr: -------------------------------------------------------------------------------- 1 | module CRZ::Prelude 2 | def id(a : A) forall A 3 | a 4 | end 5 | 6 | def map(f : (A -> B), a : Functor(A)) : Functor(B) forall A, B 7 | a.map &f 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/crz/result.cr: -------------------------------------------------------------------------------- 1 | module CRZ::Containers 2 | alias Ok = Result::Ok 3 | alias Err = Result::Err 4 | adt Result(T, E), 5 | Ok(T), 6 | Err(E) do 7 | include Monad(T) 8 | 9 | def self.of(value : T) : Result(T, E) 10 | Result::Ok(T, E).new value 11 | end 12 | 13 | def bind(&block : T -> Result(U, E)) : Result(U, E) forall U 14 | Result.match self, { 15 | [Ok, x] => (block.call x), 16 | [Err, e] => Result::Err(U, E).new e, 17 | } 18 | end 19 | 20 | def map(&block : T -> U) : Result(U, E) forall U 21 | Result.match self, { 22 | [Ok, x] => (Result::Ok(U, E).new (block.call x)), 23 | [Err, e] => Result::Err(U, E).new e 24 | } 25 | end 26 | 27 | def unwrap() : T 28 | Result.match self, { 29 | [Ok, x] => x, 30 | [Err, e] => raise Exception.new("Tried to unwrap Result::Err value") 31 | } 32 | end 33 | 34 | def has_value() : Bool 35 | Result.match self, { 36 | [Ok, x] => true, 37 | [_] => false 38 | } 39 | end 40 | end 41 | end -------------------------------------------------------------------------------- /watch_test.sh: -------------------------------------------------------------------------------- 1 | ## gem install filewatcher 2 | crystal spec 3 | filewatcher "**/*.cr" "crystal spec" 4 | --------------------------------------------------------------------------------