Skip to content

Instantly share code, notes, and snippets.

@rcook
Last active September 1, 2024 18:13
Show Gist options
  • Select an option

  • Save rcook/dd4db779353cde36646c3a78736ebb2d to your computer and use it in GitHub Desktop.

Select an option

Save rcook/dd4db779353cde36646c3a78736ebb2d to your computer and use it in GitHub Desktop.
Scalaz experimentation
#!/usr/bin/env stack
-- stack --resolver=lts-12.6 script
module Main (main) where
data Expr =
Val Int
| Add Expr Expr
| Sub Expr Expr
| Mul Expr Expr
| Div Expr Expr
eval :: Expr -> Maybe Int
eval (Val x) = pure x
eval (Add ex ey) = (+) <$> eval ex <*> eval ey
eval (Sub ex ey) = (-) <$> eval ex <*> eval ey
eval (Mul ex ey) = (*) <$> eval ex <*> eval ey
eval (Div ex ey) = do
y <- eval ey
if y == 0
then Nothing
else do
x <- eval ex
pure (x `div` y)
main :: IO ()
main = do
-- Divide by 2: displays "Just 7"
print $ eval (Div (Mul (Val 2) (Add (Val 3) (Val 4))) (Val 2))
-- Divide by 0: displays "Nothing"
print $ eval (Div (Mul (Val 2) (Add (Val 3) (Val 4))) (Val 0))
/*
* High-level commentary:
* This example suggests that Java is, in many ways, an assembly
* language. The resulting Java code is the equivalent Scala or
* Haskell code under a desugaring transform.
*/
package org.rcook;
import java.util.Optional;
import java.util.function.BiFunction;
interface Visitor<T> {
T visit(Val e);
T visit(Add e);
T visit(Sub e);
T visit(Mul e);
T visit(Div e);
}
interface Expr {
<T> T accept(Visitor<T> visitor);
}
final class Val implements Expr {
private final int value;
public Val(int value) {
this.value = value;
}
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
public int getValue() {
return value;
}
}
final class Add implements Expr {
private final Expr left;
private final Expr right;
public Add(Expr left, Expr right) {
this.left = left;
this.right = right;
}
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
public Expr getLeft() {
return left;
}
public Expr getRight() {
return right;
}
}
final class Sub implements Expr {
private final Expr left;
private final Expr right;
public Sub(Expr left, Expr right) {
this.left = left;
this.right = right;
}
@Override
public <T> T accept(final Visitor<T> visitor) {
return visitor.visit(this);
}
public Expr getLeft() {
return left;
}
public Expr getRight() {
return right;
}
}
final class Mul implements Expr {
private final Expr left;
private final Expr right;
public Mul(Expr left, Expr right) {
this.left = left;
this.right = right;
}
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
public Expr getLeft() {
return left;
}
public Expr getRight() {
return right;
}
}
final class Div implements Expr {
private final Expr left;
private final Expr right;
public Div(Expr left, Expr right) {
this.left = left;
this.right = right;
}
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
public Expr getLeft() {
return left;
}
public Expr getRight() {
return right;
}
}
public final class Main {
private static final class EvalVisitor implements Visitor<Optional<Integer>> {
@Override
public Optional<Integer> visit(Val e) {
return Optional.of(e.getValue());
}
@Override
public Optional<Integer> visit(Add e) {
return binOp(e.getLeft(), e.getRight(), (l, r) -> l + r);
}
@Override
public Optional<Integer> visit(Sub e) {
return binOp(e.getLeft(), e.getRight(), (l, r) -> l - r);
}
@Override
public Optional<Integer> visit(Mul e) {
return binOp(e.getLeft(), e.getRight(), (l, r) -> l * r);
}
@Override
public Optional<Integer> visit(Div e) {
return e
.getRight()
.accept(this)
.filter(r -> r != 0)
.flatMap(r -> e
.getLeft()
.accept(this)
.flatMap(l -> Optional.of(l / r)));
}
private Optional<Integer> binOp(
Expr left,
Expr right,
BiFunction<Integer, Integer, Integer> op) {
return left
.accept(this)
.flatMap(l -> right
.accept(this)
.flatMap(r -> Optional.of(op.apply(l, r))));
}
}
// Use Optional<Integer> since OptionalInt doesn't support flatMap etc.
private static Optional<Integer> eval(Expr e) {
EvalVisitor v = new EvalVisitor();
return e.accept(v);
}
public static void main(String[] args) {
// Divide by 2: outputs "Optional[7]"
System.out.println(eval(
new Div(
new Mul(
new Val(2),
new Add(
new Val(3),
new Val(4))),
new Val(2))));
// Divide by 0: outputs "Optional.empty"
System.out.println(eval(
new Div(
new Mul(
new Val(2),
new Add(
new Val(3),
new Val(4))),
new Val(0))));
}
}
open Core.Option
open Printf
type expr =
| Val of int
| Add of expr * expr
| Sub of expr * expr
| Mul of expr * expr
| Div of expr * expr
let rec to_sexpr = function
| Val x -> string_of_int x
| Add (left, right) ->
sprintf "(add %s %s)" (to_sexpr left) (to_sexpr right)
| Sub (left, right) ->
sprintf "(sub %s %s)" (to_sexpr left) (to_sexpr right)
| Mul (left, right) ->
sprintf "(mul %s %s)" (to_sexpr left) (to_sexpr right)
| Div (left, right) ->
sprintf "(div %s %s)" (to_sexpr left) (to_sexpr right)
let rec eval = function
| Val x -> Some x
| Add (left, right) -> binOp left right (+)
| Sub (left, right) -> binOp left right (-)
| Mul (left, right) -> binOp left right ( * )
| Div (left, right) -> binOpM left right (fun l r ->
if r = 0
then None
else Some (l / r))
and binOp left right f = map2 (eval left) (eval right) f
and binOpM left right f =
eval left >>= fun l ->
eval right >>= fun r ->
f l r
let string_of_option f o = match o with
| None -> "None"
| Some x -> sprintf "Some %s" (f x)
let () =
(* Divide by 2 *)
let e = Div (Mul (Val 2, Add (Val 3, Val 4)), Val 2) in
print_endline (to_sexpr e);
print_endline (string_of_option string_of_int (eval e));
(* Divide by 0 *)
let e = Div (Mul (Val 2, Add (Val 3, Val 4)), Val 0) in
print_endline (to_sexpr e);
print_endline (string_of_option string_of_int (eval e));
mod expr {
use Expr::*;
#[derive(Debug)]
pub enum Expr {
Val(i32),
Add(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
Div(Box<Expr>, Box<Expr>),
}
pub fn val(value: i32) -> Box<Expr> {
Box::new(Val(value))
}
pub fn add(left: Box<Expr>, right: Box<Expr>) -> Box<Expr> {
Box::new(Add(left, right))
}
#[allow(dead_code)]
pub fn sub(left: Box<Expr>, right: Box<Expr>) -> Box<Expr> {
Box::new(Sub(left, right))
}
pub fn mul(left: Box<Expr>, right: Box<Expr>) -> Box<Expr> {
Box::new(Mul(left, right))
}
pub fn div(left: Box<Expr>, right: Box<Expr>) -> Box<Expr> {
Box::new(Div(left, right))
}
}
fn eval(e: Box<expr::Expr>) -> Option<i32> {
use expr::Expr::*;
match *e {
Val(value) => Some(value),
Add(left, right) => {
let l = eval(left)?;
let r = eval(right)?;
Some(l + r)
}
Sub(left, right) => {
let l = eval(left)?;
let r = eval(right)?;
Some(l - r)
}
Mul(left, right) => {
let l = eval(left)?;
let r = eval(right)?;
Some(l * r)
}
Div(left, right) => {
let r = eval(right)?;
if r == 0 { return None }
let l = eval(left)?;
Some(l / r)
}
}
}
fn main() {
use expr::*;
{
let e = div(mul(val(2), add(val(3), val(4))), val(2));
println!("e={:?}", eval(e)) // Divide by 2: displays "Some(7)"
}
{
let e = div(mul(val(2), add(val(3), val(4))), val(0));
println!("e={:?}", eval(e)) // Divide by 0: displays "None"
}
}
import scalaz._
import std.option._
sealed trait Expr
case class Val(x: Int) extends Expr
case class Add(x: Expr, y: Expr) extends Expr
case class Sub(x: Expr, y: Expr) extends Expr
case class Mul(x: Expr, y: Expr) extends Expr
case class Div(x: Expr, y: Expr) extends Expr
object Main {
private def eval(e: Expr): Option[Int] =
e match {
case Val(x) => some(x)
case Add(ex, ey) => binOp(ex, ey)(_ + _)
case Sub(ex, ey) => binOp(ex, ey)(_ - _)
case Mul(ex, ey) => binOp(ex, ey)(_ * _)
case Div(ex, ey) =>
eval(ey)
.flatMap(y =>
if (y == 0)
none
else
eval(ex).map(_ / y))
/*
// Equivalent for-comprehension
case Div(ex, ey) => for {
y <- eval(ey)
if (y != 0)
x <- eval(ex)
} yield (x / y)
*/
}
private def binOp(ex: Expr, ey: Expr): ((Int, Int) => Int) => Option[Int] = {
Apply[Option].apply2(eval(ex), eval(ey))
}
def main(args: Array[String]): Unit = {
// Divide by 2: displays "Some(7)"
println(eval(Div(Mul(Val(2), Add(Val(3), Val(4))), Val(2))))
// Divide by 0: displays "None"
println(eval(Div(Mul(Val(2), Add(Val(3), Val(4))), Val(0))))
}
}
The MIT License (MIT)
Copyright (c) 2019 Richard Cook
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment