GithubHelp home page GithubHelp logo

fika-lang / fika Goto Github PK

View Code? Open in Web Editor NEW
224.0 4.0 13.0 258 KB

A statically typed functional programming language for the web.

License: Apache License 2.0

Elixir 99.78% HTML 0.20% Shell 0.02%
statically-typed functional-programming programming-language erlang elixir

fika's Introduction

FOSSUnited    Discord


Fika is a modern programming language for the web. It is statically typed, functional and runs on the BEAM (Erlang VM).

Current status

Actively developed. Not ready for production use or HackerNews. Currently I'm working on an incremental compiler on a long running branch.

If you'd like to keep tabs on our progress, please subscribe to our updates here.

Syntax

Here's a quick walkthrough of Fika's syntax: example.fi

Running Fika programs

Fika is written in Elixir, so make sure you have that installed. Follow these instructions to install Elixir. Next, clone this repo, cd into the directory and then follow the below instructions.

Using Elixir shell

# Install dependencies and run the Elixir shell
mix deps.get
iex -S mix

# In the Elixir shell, compile and load a Fika file using the following:
> Fika.Code.load_module("example")

# Now you can run functions from the module like this:
> :example.sum(40, 2)
> 42

Using fika executable

# Create the executable
mix release

# The above command creates an executable in the path ./_build/prod/rel/bakeware/fika
# Call the function example.sum(1, 2) from the file example.fi
./_build/prod/rel/bakeware/fika exec --function="foo()" example

PS: If you're developing Fika, the recommended way to try Fika code is to use the Elixir shell which is documented above because this is faster.

Your first HTTP server

Fika comes with a web server which allows you to quickly create HTTP request handlers. Note: This web server is a prototype currently and only responds with strings and a 200 status code.

# Inside examples/router.fi

fn routes : List({method: String, path: String, handler: Fn(->String)}) do
  [
    {method: "GET", path: "/", handler: &hello},
    {method: "GET", path: "/foo", handler: &bar}
  ]
end

fn hello : String do
  "Hello world"
end

fn bar : String do
  "Bar"
end

Now start the webserver in one of two ways:

Using Elixir shell

iex -S mix
# A web server is started automatically using the router `examples/router.fi`

Using fika executable

# Create the executable
mix release

# router.fi is in the `examples` folder
cd examples
../_build/prod/rel/bakeware/fika

Now open http://localhost:9090 in the browser to see "Hello world" served by Fika. Changes to the router are picked up automatically.

Fika together!

If you'd like to be part of the Fika community, you can find us here:

Discord server
This is the best place to chat with Fika developers, ask questions or get guidance on contributing to Fika. We also livestream some talks and pair programming sessions here. Here's the link to join.

Hackers list
This is an email digest where we send out the latest updates about Fika and our ecosystem. Here's the link to subscribe.

If you'd like to contact the creator of Fika, you can find Emil Soman on twitter or drop a mail to [email protected].

Thanks

Fika's development is supported by its many contributors and the grant from FOSSUnited. Thank you!

fika's People

Contributors

atul9 avatar emilsoman avatar fabionebbia avatar gkpacker avatar meerasndr avatar michallepicki avatar polvalente avatar sreecodeslayer avatar v0idpwn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

fika's Issues

Better error message when a dependency function fails type check

When the type check fails for a function, its dependents also fail type check, but they reuse the same error message. Expected behavior - If a function calls another function which fails type check, the caller's type check error should tell the user which dependency failed type check.

Motivation in README

It would be great to see why authors are making a new language, which languages inspire them and in which aspects new on ErlangVM-based language will be better then alternatives (Caramel and others)

Add fixed OTP and Elixir versions

It would be a good idea to add an asdf-vm .tool_versions file so everyone develops on the same Elixir and OTP versions.
It's best if the versions are the same as the ones used in the CI workflow (currently WIP at #13)

Prefix all fika modules with "fika."

To avoid clashes, we should prefix all modules compiled from fika code with "fika." just like "Elixir.". This should be transparent to the user, ie, these modules when used in fika code don't need the prefix and the compiler automatically adds the prefix.

Add documentation for new contributors

  1. Add a "Contributing" section to the README.
    This section should explain how to install all dependencies and get started with the dev environment for the Fika compiler. See the README of https://github.com/elixir-lang/elixir for ideas.

  2. Create a docs/architecture.md file explaining how Fika works (DM emil on discord for help) and link it in the new README section.

Add tuples

Tuples are of the form {"foo", 123} which is of the type: {String, Int}. They are like lists, but what they will contain is already fixed at compile time and cannot contain arbitrary values.

creating fika executable is failing

creating fika executable failing with the following error.

$ mix escript.build
** (File.Error) could not write to file "fika": illegal operation on a directory
    (elixir 1.11.3) lib/file.ex:1053: File.write!/3
    (mix 1.11.3) lib/mix/tasks/escript.build.ex:193: Mix.Tasks.Escript.Build.escriptize/2
    (mix 1.11.3) lib/mix/task.ex:394: Mix.Task.run_task/3
    (mix 1.11.3) lib/mix/cli.ex:84: Mix.CLI.run_task/2

I think this is happening because it is creating a file named fika, but there is a directory in the working dir with the same name.

Not sure if fika/io.fi is used anywhere.

Add support for generics

Lowercase strings in type signatures can be parsed as type variables and be used to write functions like reverse(List(t)) : List(t) or map(List(a), Fn(a -> b)) : List(b) etc

Syntax for using modules as dependencies in other modules

Syntax will look like: use <path_relative_to_root> as <alias>.
Examples: use fika/parser as Parser, use deps/my_library as MyLibrary.

We already infer module names from their filesystem paths. This is similar to how Gleam modules work.

One important detail that's worth discussing:
Should we use snake_case for module names or CamelCase? If we use snake_case, we can allow users to just write use foo/my_module instead of use foo/my_module as Parser. But the advantage of using CamelCase for module names is that it's easier on the eyes for people coming from Elixir. One could ask why not automatically create CamelCase module names instead of asking the user and the answer is - we want to make it explicit.

Why use instead of alias? A new programmer should be able to understand what "using" a module means, also it's shorter to type. Why do we not import functions? When looking at a function call, the user should be able to easily trace back where it's defined. This means, do not import other modules' functions into the current module so they look like local functions.

The compiler will throw an error if a user calls a function Foo.bar without first mentioning use <module path> as Foo

Add support for union types

A union type is a set consisting of multiple non-overlapping types. With union types, we can have function signatures of the type:

fn foo : {:ok, Int} | {:error, String} do
  if true do
    {:ok, 123}
  else
    {:error, "Something went wrong"}
  end
end

We'll need to understand the concept of subtypes before we start working on this. This wiki is a good introduction.

In this example:
image
duck is a subtype of bird (which is its supertype), which means it can be substituted wherever a bird is expected. Similarly, a union type is a supertype of each member of the set.

Changes required:

  1. Allow if-else branches to return different types. The inferred type of the if-else expression will be a union of the two types. This can be represented using a new "Union" struct that can be defined here like the FunctionRef struct. The to_string function of the struct will join the types separated by " | ".
  2. When inferring the return type of a function, we'll now expand union types and add all possible function types in add_function_type. Example:
# A function that takes x which can be either :foo or :bar and returns either :ok or :error
fn foo(x: :foo | :bar) : :ok | :error do
  # ...
end

The type checker will add the following function types to its "function_types" map in env:

  1. module.foo(:foo) => :ok
  2. module.foo(:foo) => :error
  3. module.foo(:bar) => :ok
  4. module.foo(:bar) => :error

We can optimize the storing and checking of function types later, but this should do for now.

Please let me know if I've missed something here!

Improve bakeware releases

  1. Use the bakeware release from hex instead of depending on their old spawnfest github source
  2. Generate the executable into a directory named "dist" because our stdlib will be inside "fika" directory and the name's gonna clash.
  3. Use prod env (also follow other bakeware instructions) to make the executable smaller.

Create a playground app to try Fika

Inspired by https://play.golang.org/ and https://elm-lang.org/try , this is a way for users to run Fika code using their browser and get a taste of Fika's features. I'm thinking:

UI

We'll need two panes:

  1. Code editor - this is where users will write Fika code.
  2. Compiler output/Console - This shows the compiler output if there's an error, otherwise shows a form that allows users to invoke functions and shows the results.

On the header, there would be a dropdown that allows users to select an example from a list which replaces the content of the editor with the example code. There will also be a button "Compile" which compiles the code in the editor.

Sandboxing

  • Do not allow arbitrary external functions and side effects - this can be disallowed by the typechecker (only in the playground environment).
  • Scope modules and requests using unique session ids and purge modules after sessions become stale.

The code will go into a separate repository (fika-lang/playground) which I'll create and open up for contributions after this issue is ready for development.

Thoughts and ideas welcome!

Cannot parse functions whose names begin with a keyword

The following examples cause parser error

  • fn fnwathever do
  • fn dowhatever do
  • fn ifwhatever do
  • fn endwhatever do
  • fn elsewhatever do

Think problem lies in Fika.Parser.Common:

  keyword =
    choice([
      string("fn"),
      string("do"),
      string("end"),
      string("if"),
      string("else")
    ])

  ...

  identifier =
    lookahead_not(keyword)        <--- HERE
    |> concat(identifier_str)
    |> label("identifier")
    |> Helper.to_ast(:identifier)

Opening this issue 'cause I couldn't come up with ways to fix it that didn't involve some levels of space handling refactoring.

Add evaluation tests for expressions

We need a test/fika/exp_eval_test.exs which tests the evaluation result of all supported expressions. To see all the expressions supported by Fika, look at the parsec "exp" defined in Fika.Parser.Expressions.

Type checker sees arguments of function calls in reversed order

If you have a function that accepts multiple arguments of different types

fn foo(a1: Type1, ..., an: TypeN)

and you call it, you'll get

{:error, "Undefined function: <module>.foo(TypeN, ..., Type1) in module <module>"}

e.g.

fn foo(a: Int, b: String) : Int do
  a
end

fn bar : Int do
  foo(5, "a")
end

results in

{:error, "Undefined function: sandbox.foo(String, Int) in module sandbox"}

Note: the call's AST (below) looks good (the order of the args is preserved) so I suppose the problem lies in the type checker

{:call, {:foo, _}, [{:integer, _, 5}, {:string, _, ["a"]}], nil}

Improve documentation for new contributors

This issue is on me. I need to talk to Fika contributors to understand the challenges they faced when getting started and improve the documentation/process around submitting a PR. All (potential) contributors are welcome to add comments on this issue if they have ideas on making it easier to get started with contributing to Fika.

  • Add a PR template
  • Add documentation on how to add tests
  • Let contributors know who to get in touch with and how, regarding different parts of the code
  • Add a wiki page about the vision, goals and design decisions of Fika

Add literal types

Literals are represented by :<identifier>. These can be used in types. For example a function can always return :ok and the signature will be: fn foo : :ok

Better data structures for types

Right now, types are represented as strings in our AST. For example, the type of [1, 2, 3] is the string "List(Int)". Right now this is fine because type checking is as simple as a string comparison with the expected type, but soon this will be insufficient because we'll have union types. It's a good idea to move to better data structures for holding types before we merge in union types.

Here's the list of all the types we currently have and the proposed data structure to hold them in the AST. So instead of just strings, we'll now have atoms, strings and structs representing types.

Booleans - atom :Bool
Atoms (:foo)- string ":<atom>"
Integers - atom :Int
Strings - atom :String
Lists (List(<type>)) - %List{type: <type>}
Records ({foo: <type>, ...}) - %Record{fields: [{<key_atom>, <value_type>}, ...]}
Tuples {<type>, ...} - %Tuple{elements: [<type>, ...]}
Function references - %FunctionRef{arg_types: [<type>, ...], return_type: <type>}
Nothing - atom :Nothing

Add support for recursion

The following should type check successfully:

    fn a : Loop(Int | String) do
      if true do
        b()
      else
        123
      end
    end

    fn b : Loop(Int | String) do
      if true do
        c()
      else
        "abc"
      end
    end

    fn c : Loop(Int | String) do
      a()
    end

    fn d : Int | String do
      a()
    end

I noticed there was no issue for this, but @polvalente is already working on this in #87 so just adding it here.

Add support for maps

Maps will have the following syntax:

{"foo" => 123} # Type: Map(String, Int)

Don't worry about empty maps for now. We'll handle that separately. Please refer the Fika language hacking guide inside /docs to understand the steps involved in adding new syntax to Fika.

Add boolean data type

Fika currently doesn't have booleans, and that should change. To add booleans, we'll have to do these:

1. Make the parser understand the strings "true" and "false" and create "boolean"-type nodes in the AST when they're read.

The goal is to make these tests pass:

describe "boolean" do
  test "true" do
    str = "true"

    result = Parser.expression!(str)
    assert result ==  {:boolean, {1, 0, 4}, true}
  end

  test "false" do
    str = "false"

    result = Parser.expression!(str)
    assert result ==  {:boolean, {1, 0, 5}, false}
  end
end

Note that these tests don't currently exist, and need to be added to test/fika/parser_test.exs.

We'll look at how to make these tests pass. First, we'll have to open the file lib/fika/parser.ex. What you see in this file is actually a parser combinator written using a library called NimbleParsec. A parser combinator uses several smaller parsers to give you a big parser. In order to parse booleans, we're going to write a small parser for just that and plug it into the right place in the bigger parser.

A parser for true | false looks like this:

boolean =
  choice([
    string("true"),
    string("false")
  ])

Now if you look at other parsers like the one for integer, you can see there's a final step of transforming the results into the AST using Helper.to_ast call. This generates the AST node that will represent what's parsed. So we'll add a new function in lib/fika/parser_helper.ex that returns a node that looks like {:boolean, line, true}

This should be enough to pass the parser tests. We can run the tests using mix test test/fika/parser_test.exs.
The next step is to make the type checker aware of this new AST node.

2. Type check booleans

Our goal is to make these tests pass:

# These tests go to test/fika/type_checker_test.exs
describe "boolean" do
  test "true" do
    str = "true"
    ast = Fika.Parser.expression!(str)

    assert {:ok, "Bool", _} = TypeChecker.infer_exp(Env.init(), ast)
  end

  test "false" do
    str = "false"
    ast = Fika.Parser.expression!(str)

    assert {:ok, "Bool", _} = TypeChecker.infer_exp(Env.init(), ast)
  end
end

In order to do this, we'll open the file for the type checker module
and add a function that infers "Bool" types just like how the "Int" type is inferred.

3. Translate to Erlang Abstract Format

In this step, we convert Fika AST to Erlang Abstract Format.
Booleans are represented as {:atom, line, true|false} in Erlang abs form.
We need to make this test pass:

# These tests go to test/fika/erl_translate_test.exs
test "boolean" do
  str = "true"
  ast = Parser.expression!(str)
  result = ErlTranslate.translate_expression(ast)

  assert result == {:atom, 1, true}
end

Open lib/fika/erl_translate.ex
and add a function that converts boolean nodes to the format like the above.
Refer how integers are translated to get an idea.

4. Run the code

If all went well, we should be able to write code with booleans. Let's try that.
Open example.fi and add a function that returns a boolean. Leave out the
return type for the function definition for now. Now open the elixir shell
using iex -S mix (-S mix loads the Fika project) and run Fika.Code.load_file("example.fi")
The type checker should catch the wrong return type. Let's fix that by specifying
Bool as the return type and reload the file. If the file was successfully compiled
and loaded, we can now call the function using :example.function_name() from
the shell.

5. Update README

There's a section in the README that talks about data types in Fika and shows
examples. Update that part with the shiny new feature you just added. It's
time to get your pull request merged.

Tasks

To recap, here are the tasks involved:

  • Update Parser
  • Update Type checker
  • Update Erl translator
  • Update README

All the best! If you need help, ping me in the discord server.

Add mix format --check-formatted as CI check

As per my comment on #13, I think that formatter and credo would be nice additions to the workflow.
Since the formatter is standardized, it would be nice and simple for everyone to use.
And credo adds more code uniformity checks that might also be nice

Optimize parser compilation time

Currently the parser combinator takes a while to compile because nimble_parsec will take quite a bit of time to create functions from all our rules. Got some pointers from Jose Valim to optimize this. One way is to split large combinators into smaller ones and put them in multiple modules. Because modules are compiled in parallel, this will have a significant effect in compilation times. Also explore the possibility of pre-compiling rarely changed parsers (white spaces, identifiers etc) and checking in the compiled functions to git.

Run tests on CI

The goal is to make sure tests pass when code is pushed or when PRs are opened and merged. Make sure to run mix compile with mix compile --warnings-as-errors to make sure there are no compilation warnings.

String interpolation doesn't compile

To be more precise, it seems like string interpolation does compile as long as:

  • the "outer string" is empty ("<--nothing here-->#{anything}<--nothing here-->");
  • it's used only once ("#{first_usage}" is ok, "#{first_usage}#{second_usage}" is not).

Examples:

  • Single interpolated string with nothing outside: "#{"string"}" compiles ✅
  • Single interpolated variable with nothing outside:"#{var}" compiles ✅
  • Single interpolated string with anything outside: "Hello #{"World"}" doesn't compile ❌
  • Single interpolated variable with anything outside: "Hello, #{name}!" doesn't compile ❌
  • Multiple interpolated string/variables with or without anything outside: "#{var}#{"string"}" doesn't compile ❌

Note that any of this examples gets parsed as intended, the error seems to arise during type checking.

Add if-else expression

Syntax:

if <exp> do
  exps
else
  exps
end

if is always followed by an else. This can be parsed into the AST node {{:if, line}, condition_exp, true_exps, false_exps}

Type checking:

The true_exps and false_exps can return two different things which make the return type a union type (example: {:ok, Int} | {:error, String}), but we don't have union types now, so for now we'll just make sure they both return the same type. In short, the type checks needed for now:

  1. The condition_exp should be a boolean.
  2. The return types of true_exps and false_exps should be the same.

Once these checks pass, the type of the if-else expression is inferred as the type of the true_exps or false_exps blocks.

Erl translate

In Erl Abs form, this can be represented as a case node.

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.