Giter Club home page Giter Club logo

hafly's Introduction

hafly

hafly (pronounced "half-lee") is a simple dynamic embeddable scripting language in Haskell.

You can try it out on the web repl, which is itself a Haskell webapp implemented with Reflex. (The implementation can be found here.)

Please note that hafly is still in pre-alpha, and the design, feature set, and syntax are by no means stable.

Why?

I wanted a simple, easy to learn scripting language that was easy to embed in pure Haskell projects (so it's easy to target, for instance, ghcjs as well as native targets) that still meshes well with Haskell idioms. (Simple expression language + a monadic block syntax that can be interpreted in an arbitrary monad? Hell yeah!) As far as I could tell, such a thing did not exist -- so I made one!

Comparison with other projects:

  • hslua: Easy for beginners, embeddable, yet is not pure haskell (hard to use in GHCJS).
  • codeworld haskell: Possibly another good option in this space -- an educational variant of Haskell that can be run in GHCJS.
  • hint: Very nice -- yet pretty heavyweight, and Haskell itself is not nescesarialy the easiest for beginners.
  • Various lisp/scheme implementations: Maybe not the easiest to embed. Arguably not the most intuitive syntax for beginners. Too dissimilar to Haskell.

Why dynamically typed?

I wanted something quick and no-nonsense (implementation-wise), and easy to extend with arbitrary Haskell types and functions without mucking about with a bunch of singletons and/or type families -- building a dynamic language at first seemed like the easiest way to do that.

Given sufficent time (and/or interested contributors!) I'll probably add optional static type checking capabilities in the future.

Features / Progress

  • Naturally sandboxed and extensible.
  • Dynamic (single) dispatch.
  • Multiple Dispatch (i.e. the dymically-typed version of multi-parameter type classes)
  • Higher-order functions
  • Everything is an expression
  • Recursion
  • What if Haskell... but with a Rust-like monadic syntax thrown in for good measure? (But I might add whitespace sensitivity later, IDK)
  • Kotlin-esque string templating.
  • IORef-backed ML-like references (only in IO!)
  • Sequential blocks that can be bound to any monad.
  • Flexible binding of names in sequential blocks (do notation++)โ„ข.
  • Symbols and polymorphic variants.
  • Simple pattern matching.
  • Flexible records -- because we're not cavemen.
  • Record dot and Uniform Function Call Syntax.
  • Functorial polymorphism (A generalizaton of Array Programming Languages)
  • Monadic polymorphism (Compare effect polymorphism in koka)

Examples

Recursive Function Definitions

fact = \n -> if(n == 0) 1 
    else n * fact (n - 1)

Function Application Operator

squared = \x -> x * x

> squared $ squared 2.5
39.0625

Function Composition

f = \x -> x + 2
g = \x -> x * 5

h  = g of f
h' = f then g

> h 1
15

> h' 1
15

Heterogenous Collections

[1, 2, "hello", 4.2, [1, 2, "x", "y"]]

Uniform Function Call Syntax

squared = \x -> x * x

> 4.squared
8

> 2.5.squared.squared
39.0625

ML-like References (Mutable Variables)

main = {
    x <- var 0;
    printLn "The value of x is $x.";
    printLn "Enter in a new value for x.";
    newValue <- readLn then toInt;
    x := newValue;
    printLn "The value of x is now $x."
}

Scheduling Task

-- Interperted in a monad allowing for the scheduling of 
-- actions in IO.
remindMe time timeToRemindAgain todo = schedule time {
    result <- prompt "Don't forget! $todo";
    case result {
        Ok -> done;
        Cancel -> {
            schedule timeToRemindAgain {
                remindMe timeToRemindAgain timeToRemindAgain todo
            }
        };
    }
}

User Interface

-- Interpreted in a "builder" monad for a UI.
-- builds up a record of the form:
--    [ name: "bob", age: 42 ]
entryForm = Column {
    Row {
        Text "Name: ";
        nameEntry <- TextEntry
            .bind name
    };
    Row {
        Text "Age: ";
        ageEntry <- TextEntry
            .bind age
    }
}

Do Notation++ (WIP)

UI = Column {
    Row {
        Text "Click the button:";
        button <- Button ":)"
    };
    
    when button.clicked {
        popupDialog "You clicked the button!"
    }
}

is equivalent to the following:

UI = Column {
    button <- Row {
        Text "Click the button:";
        button <- Button ":)";
        return button
    };
    
    -- Variables in nexted monadic blocks are automatically accessible in parent scopes
    when button.clicked {
        popupDialog "You clicked the button!"
    }
}

Reactive Polymorphism (WIP)

Let's say you have a type of reactive state variables State a which is a functor (in Hafly terms, we've defined a map operator for it via multiple dispatch), and a state : a -> m (State a) to introduce them in some monad m. Hafly can automatically map and bind operations over States without extra ceremony:

UI = Column {
    count <- state 0
    
    btn <- Button "Click me!"
    
    when btn.clicked {
        count := count + 1
    }
    
    Text "You clicked me $count times!"
}

Compare with the "manual" version, where we have to apply map rather than directly operating over the State:

UI = Column {
    count <- state 0
    
    btn <- Button "Click me!"
    
    when btn.clicked {
        count := count + 1
    }
    
    btnText = count.map $ \num ->
        "You clicked me $num times!"
    
    Text btnText
}

How does it work?

To set up your own embedded interpreter, you need to build an InterpreterContext -- which contains all the data (such as "built-in" functions and operators) needed to interpret a hafly program. Hafly comes with an optional base :: InterpreterContext "standard library" of sorts that you can use to get started -- but this can be customized to your own needs.

Once you have that, with interpret :: InterpreterContext -> Ast -> Either TypeError Dynamic you can take a hafly Ast and interpret it as a Dynamic value from Data.Dynamic. You can then attempt to grab a value of some concrete Haskell type out of that Dynamic with Data.Dynamic's fromDynamic.

Hafly comes with a few built-in functions for interpreting hafly programs in specific contexts. For instance, interpretIO can be used to interpret a hafly program representing an IO action.

hafly's People

Contributors

sintrastes avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

hafly's Issues

Fix issues with currying

Currently, currying seems to be causing issues, e.x.

f = \x y -> x
g = f 2

> g 3
3 -- (expected 2)

Allow for `Dynamic`s to be displayed properly.

Currently, we have:

> [1,2,3,4.2]
[<<Int>>,<<Int>>,<<Int>>,<<Double>>]

whereas it would be nice if we had:

> [1, 2, 3, 4.2]
[1, 2, 3, 4.2]

Maybe the best approach for this would be to have the interpreter see if there is a show instance for each element of the list, and try to display it that way -- otherwise, falling-back to just displaying the type?

Allow way of specifying variance of Haskell data types.

In order to properly act on Dynamic data with higher-order functions, we'll probably need to consider variance (e.x. implicitly lifting a [Int] to a [Dynamic]. We want this to be able to be specified for an arbitrary Haskell data type to keep things extensible.

This may also be relevant for features like functor/applicative/monad polymorphism. e.x. if f : Int -> Int and x: [Int], we could say f x, and have that automatically lifted to map f x. This I think is the general idea behind what I was thinking of as "reactive polymorphism".

Implement record accessors.

We'll need some way to disambiguate these from function calls, maybe something like:

For an expression x.y:

  1. If x is a record, first check to see if it has any fields y. If so, use y as a record accessor for that field.
  2. Otherwise, check to see if there is a function y that could act on x.

Allow for user-defined multiple dispatch

Currently multiple dispatch only works on library-defined functions.

I believe one reason for this is that the user currently has no way of being able to specify type signatures.

We should probably make our type signatures mirror that of Haskell's.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.