Giter VIP home page Giter VIP logo

fp-course's Introduction

Functional Programming Course

Data61

Written by Tony Morris & Mark Hibberd for Data61 (formerly NICTA)

With contributions from individuals (thanks!)

Special note 1

If you have arrived here by https://github.com/data61/fp-course and you are looking for the answers (not the exercises), please go to https://github.com/tonymorris/fp-course

Special note 2

As of February 2017, this repository is taking the place of the repository hosted at https://github.com/NICTA/course which is deprecated.

Data61 replaces what was NICTA since July 2016. The new repository is located at https://github.com/data61/fp-course.

Introduction

The course is structured according to a linear progression and uses the Haskell programming language to learn programming concepts pertaining to functional programming.

Exercises are annotated with a comment containing the word "Exercise." The existing code compiles, however answers have been replaced with a call to the Haskell error function and so the code will throw an exception if it is run. Some exercises contain tips, which are annotated with a preceding "Tip:". It is not necessary to adhere to tips. Tips are provided for potential guidance, which may be discarded if you prefer a different path to a solution.

The exercises are designed in a way that requires personal guidance, so if you attempt it on your own and feel a little lost, this is normal. All the instructions are not contained herein.

Getting Help

There are two mailing lists for asking questions. All questions are welcome, however, your first post might be moderated. This is simply to prevent spam.

  1. [nicta-fp] is a Google Group for any queries related to functional programming. This mailing list is owned by Data61 and is open to the public. Questions relating to this course are most welcome here.

  2. [haskell-exercises] is a Google Group for queries related specifically to this Data61 functional programming course material. This mailing list is not owned by Data61, but is run by others who are keen to share ideas relating to the course.

  3. #nicta-course on Freenode is an IRC channel that is operated by others who are going through this course material on their own time and effort.

  4. #qfpl on Freenode is the IRC channel of the Queensland Functional Programming Lab - the team that runs the course in Brisbane.

  5. #scalaz on Freenode is an IRC channel that is operated by others who are keen to share ideas relating to functional programming in general. Most of the participants of this channel have completed the Data61 functional programming course to some extent. They are in various timezones and share a passion for functional programming, so may be able to provide relatively quick assistance with questions.

Getting Started

NOTE If you do not wish to install these dependencies, you may use a virtual machine instead. Instructions for automatically building a virtual machine are available in this repository for your convenience.

  1. Install the Glasgow Haskell Compiler (GHC) version 7.10 or higher.

  2. Change to the directory containing this document.

  3. Execute the command ghci, which will compile and load all the source code. You may need to set permissions on the root directory and the ghci configuration file, chmod go-w .ghci ./.

  4. Inspect the introductory modules to get a feel for Haskell's syntax, then move on to the exercises starting with Course.Optional. The Progression section of this document lists the recommended order in which to attempt the exercises.

  5. Edit a source file to a proposed solution to an exercise. At the ghci prompt, issue the command :reload. This will compile your solution and reload it in the GHC interpreter. You may use :r for short.

Tips after having started

  1. Some questions take a particular form. These are called WTF questions. WTF questions are those of this form or similar:
  • What does ____ mean?
  • What does the ____ function mean?
  • What is a ____ ?
  • Where did ____ come from ?
  • What is the structure of ____ ?

They are all answerable with the :info command. For example, suppose you have the question, "What does the swiggletwoop function mean?" You may answer this at GHCi with:

> :info swiggletwoop

You may also use :i for short.

  1. Functional Programming techniques rely heavily on types. This reliance may feel foreign at first, however, it is an important part of this course. If you wish to know the type of an expression or value, use :type. For example,

    > :type reverse

    List t -> List t

    This tells you that the reverse function takes a list of elements of some arbitrary type (t) and returns a list of elements of that same type. Try it.

    You may also use :t for short.

  2. GHCi has TAB-completion. For example you might type the following:

    > :type rev

    Now hit the TAB key. If there is only one function in scope that begins with the characters rev, then that name will auto-complete. Try it. This completion is context-sensitive. For example, it doesn't make sense to ask for the type of a data type itself, so data type names will not auto-complete in that context, however, if you ask for :info, then they are included in that context. Be aware of this when you use auto-complete.

    This also works for file names:

    > readFile "/etc/pas"

    Now hit the TAB key. If there is only one existing filename on a path that begins with /etc/pas, then that name will auto-complete. Try it.

    If there is more than one identifier that can complete, hit TAB twice quickly. This will present you with your options to complete.

  3. Follow the types.

    You may find yourself in a position of being unsure how to proceed for a given exercise. You are encouraged to adopt a different perspective. Instead of asking how to proceed, ask how you might proceed while adhering to the guideline provided by the types for the exercise at hand.

    It is possible to follow the types without achieving the desired goal, however, this is reasonably unlikely at the start. As you become more reliant on following the types, you will develop more trust in the potential paths that they can take you, including identification of false paths.

    Your instructor must guide you where types fall short, but you should also take the first step. Do it.

  4. Do not use tab characters

    Set up your text editor to use space characters rather than tabs. Using tab characters in Haskell can lead to confusing error messages. GHC will give you a warning if your program contains a tab character.

  5. Do not use the stack build tool. It does not work.

Running the tests

Tests are available as a tasty test suite.

tasty

Tasty tests are stored under the test/ directory. Each module from the course that has tests has a corresponding <MODULE>Test.hs file. Within each test module, tests for each function are grouped using the testGroup function. Within each test group there are test cases (testCase function), and properties (testProperty function).

Before running the tests, ensure that you have an up-to-date installation of GHC and cabal-install from your system package manager or use the minimal installers found at haskell.org.

To run the full test suite, build the project as follows:

> cabal update
> cabal install --only-dependencies --enable-tests
> cabal configure --enable-tests
> cabal build
> cabal test

Tasty will also allow you to run only those tests whose description match a pattern. Tests are organised in nested groups named after the relevant module and function, so pattern matching should be intuitive. For example, to run the tests for the List module you could run:

> cabal test tasty --show-detail=direct --test-option=--pattern="Tests.List."

Likewise, to run only the tests for the headOr function in the List module, you could use:

> cabal test tasty --show-detail=direct --test-option=--pattern="List.headOr"

In addition, GHCi may be used to run tasty tests. Assuming you have run ghci from the root of the project, you may do the following. Remember that GHCi has tab completion, so you can save yourself some typing.

> -- import the defaultMain function from Tasty - runs something of type TestTree
> import Test.Tasty (defaultMain)
>
> -- Load the test module you'd like to run tests for
> :l test/Course/ListTest.hs
>
> -- Browse the contents of the loaded module - anything of type TestTree
> -- may be run
> :browse Course.ListTest
>
> -- Run test for a particular function
> defaultMain headOrTest

doctest

The doctest tests are a mirror of the tasty tests that reside in comments alongside the code. They are not executable, but examples can be copied into GHCI. Examples begin with >>> while properties begin with prop>.

Progression

It is recommended to perform some exercises before others. The first step is to inspect the introduction modules.

  • Course.ExactlyOne
  • Course.Validation

They contain examples of data structures and Haskell syntax. They do not contain exercises and exist to provide a cursory examination of Haskell syntax. The next step is to complete the exercises in Course.Optional.

After this, the following progression of modules is recommended:

  • Course.List
  • Course.Functor
  • Course.Applicative
  • Course.Monad
  • Course.FileIO
  • Course.State
  • Course.StateT
  • Course.Extend
  • Course.Comonad
  • Course.Compose
  • Course.Traversable
  • Course.ListZipper
  • Course.Parser (see also Course.Person for the parsing rules)
  • Course.MoreParser
  • Course.JsonParser
  • Course.Interactive
  • Course.Anagrams
  • Course.FastAnagrams
  • Course.Cheque

During this progression, it is often the case that some exercises are abandoned due to time constraints and the benefit of completing some exercises over others. For example, in the progression, Course.Functor to Course.Monad, the exercises repeat a similar theme. Instead, a participant may wish to do different exercises, such as Course.Parser. In this case, the remaining answers are filled out, so that progress on to Course.Parser can begin (which depends on correct answers up to Course.Monad). It is recommended to take this deviation if it is felt that there is more reward in doing so.

Answers for the exercises can be found here: https://github.com/tonymorris/fp-course

After these are completed, complete the exercises in the projects directory.

Leksah

If you choose to use the Leksah IDE for Haskell, the following tips are recommended:

  • Install Leksah from github. If you are using Nix to install Leksah launch it with ./leksah-nix.sh ghc822 as the Nix files for this course use GHC 8.2.2.
  • Clone this fp-course git repo use File -> Open Project to open the cabal.project file.
  • Mouse over the toolbar items near the middle of toolbar to see the names of them. Set the following items on/off:
    • Build in the background and report errors ON - unless you prefer to triger builds manualy with Ctrl + B to build (Command + B on OS X)
    • Use GHC to compile ON
    • Use GHCJS to compile OFF
    • Use GHCi debugger to build and run ON
    • Make documentation while building OFF
    • Run unit tests when building ON
    • Run benchmakrs when building OFF
    • Make dependent packages ON
  • If you are using Nix, click on the nix button on the toolbar (tool tip is "Refresh Leksah's cached nix environment variables for the active project"). This will use nix-shell to build an environment for running the builds in. If nix-shell has not been run before for the fp-course repo it may take some time to complete. When it is finished a line of green '-' characters should be printed in the Panes -> Log.
  • Restart Leksah as there is a bug in the metadata collection that will prevent it from indexing the new project without a restart.
  • Ctrl + B to build (Command + B on OS X).
  • The test failures should show up in Panes -> Errors.
  • Pane -> Log often has useful error messages.
  • Ctrl + J (Command + J on OS X) selects the next item in Errors pane and goes to it in the source (hold down Shift to go to previous item).
  • Ctrl + Enter on a line starting "-- >>>" will run the selected expression in GHCi (Ctrl + Enter on OS X too). The output goes to Panes -> Log (on Linux it will also show up in Panes -> Output).
  • The last GHCi expression is reevaluated after each :reload triggered by changes in the code.

Introducing Haskell

This section is a guide for the instructor to introduce Haskell syntax. Each of these points should be covered before attempting the exercises.

  • values, assignment
  • type signatures :: reads as has the type
    • The -> in a type signature is right-associative
  • functions are values
  • functions take arguments
    • functions take only one argument but we approximate without spoken language
    • functions can be declared inline using lambda expressions
    • the \ symbol in a lambda expression denotes a Greek lambda
  • operators, beginning with non-alpha character, are in infix position by default
    • use in prefix position by surrounding with (parentheses)
  • regular identifiers, beginning with alpha character, are in prefix position by default
    • use in infix position by surrounding with backticks
  • polymorphism
    • type variables always start with a lower-case character
  • data types, declared using the data keyword
    • following the data keyword is the data type name
    • following the data type name are zero of more type variables
    • then = sign
    • data types have zero or more constructors
      • data type constructors start with an upper-case character, or colon (:)
    • following each constructor is a list of zero or more constructor arguments
    • between each constructor is a pipe symbol (|)
    • the deriving keyword gives us default implementations for some functions on that data type
    • when constructors appear on the left side of = we are pattern-matching
    • when constructors appear on the right side of = we are constructing
  • type-classes

Learning the tools

When this course is run in-person, some tools, particularly within Haskell, are covered first.

  • GHCi
    • :type
    • :info
  • values
  • type signatures
    • x :: T is read as x is of the type T
  • functions are values
  • functions take arguments
  • functions take one argument
  • lambda expressions
  • operators (infix/prefix)
    • identifiers starting with isAlpha are prefix by default, infix surrounded in backticks (`)
    • other identifiers are infix by default, prefix surrounded in parentheses
  • data types
    • data keyword
    • recursive data types
  • pattern matching
  • deriving keyword
  • type-classes
  • type parameters
    • always lower-case 'a'..'z'
    • aka generics, templates C++, parametric polymorphism
  • running the tests
    • cabal test

Parser grammar assistance

The exercises in Parser.hs can be assisted by stating problems in a specific way, with a conversion to code.

English Parser library
and then bindParser >>=
always valueParser pure
or |||
0 or many list
1 or many list1
is is
exactly n thisMany n
fail failed
call it x \x ->

Monad comprehension

do-notation
  • insert the word do
  • turn >>= into <-
  • delete ->
  • delete \
  • swap each side of <-
LINQ
  • write from on each line
  • turn >>= into in
  • delete ->
  • delete \
  • swap each side of in
  • turn value into select

Demonstrate IO maintains referential transparency

Are these two programs, the same program?

p1 ::
  IO ()
p1 =
  let file = "/tmp/file"
  in  do  _ <- writeFile file "abcdef"
          x <- readFile file
          _ <- putStrLn x
          _ <- writeFile file "ghijkl"
          y <- readFile file
          putStrLn (show (x, y))

p2 ::
  IO ()
p2 =
  let file = "/tmp/file"
      expr = readFile file
  in  do  _ <- writeFile file "abcdef"
          x <- expr
          _ <- putStrLn x
          _ <- writeFile file "ghijkl"
          y <- expr
          putStrLn (show (x, y))

What about these two programs?

def writeFile(filename, contents):
    with open(filename, "w") as f:
        f.write(contents)

def readFile(filename):
    contents = ""
    with open(filename, "r") as f:
        contents = f.read()
        return contents

def p1():
    file = "/tmp/file"

    writeFile(file, "abcdef")
    x = readFile(file)
    print(x)
    writeFile(file, "ghijkl")
    y = readFile(file)
    print (x + y)

def p2():
    file = "/tmp/file"
    expr = readFile(file)

    writeFile(file, "abcdef")
    x = expr
    print(x)
    writeFile(file, "ghijkl")
    y = expr
    print (x + y)

One-day

Sometimes this course material is condensed into one-day. In these cases, the following exercises are recommended:

  • Optional
    • mapOptional
    • bindOptional
    • (??)
    • (<+>)
  • List
    • headOr
    • product
    • length
    • map
    • filter
    • (++)
    • flatMap
    • reverse
  • Functor
    • instance Functor List
    • instance Functor Optional
    • instance Functor ((->) t)
    • instance Functor void
  • Applicative
    • instance Applicative List
    • instance Applicative Optional
    • instance Applicative ((->) t)
    • lift2
    • sequence
  • FileIO

References

fp-course's People

Contributors

ahaxu avatar ajcoppa avatar axman6 avatar bagl avatar bens avatar bts avatar cjmazey avatar djv avatar dtchepak avatar earldouglas avatar frasertweedale avatar gwils avatar hamishmack avatar japgolly avatar kyleneideck avatar levinotik avatar lightandlight avatar markhibberd avatar mausch avatar mitochon avatar oiegag avatar parsonsmatt avatar qrilka avatar rcook avatar relrod avatar sleepynate avatar tekerson avatar tismith avatar tktan avatar tonymorris 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  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

fp-course's Issues

Missing functor import in src/Course/Monad.hs

The (>>=) excercise (https://github.com/tonymorris/course/blob/master/src/Course/Monad.hs#L143-L155) tells you to use <$>, but it is Course.Functor is not imported.
Also, the excercise already has a correct implementation using flip - this is different from every other excercise I've done which have errors with TODO messages.

Patch for import fix.

diff --git a/src/Course/Monad.hs b/src/Course/Monad.hs
index c45c471..cc71ed8 100644
--- a/src/Course/Monad.hs
+++ b/src/Course/Monad.hs
@@ -10,6 +10,7 @@ module Course.Monad(
 , (<=<)
 ) where

+import Course.Functor
 import Course.Applicative hiding ((<*>))
 import Course.Core
 import Course.Id

ListZipper.findLeft

The algorithm for ListZipper.findLeft is defined as:

Seek to the left for a location matching a predicate, starting from the current one.

However, the solution uses the break function which does not move left, but breaks on the first matching position on the left side of ListZipper, but starting from the beginning of the left list.

In other words, because of seek to the left ... starting from the current, in the example of

-- >>> findLeft (== 1) (zipper [1, 2, 1] 3 [4, 5])

shouldn't the result be

-- [1,2] >1< [3,4,5]

instead of

-- [2,1] >1< [3,4,5]

as it currently shown in the answers?

Where are the exercises?

From the docs:

Exercises are annotated with a comment containing the word "Exercise." The existing code compiles, however answers have
been replaced with a call to the Haskell `error` function and so the code will throw an exception if it is run. 

But:

$ git grep Exercise
README.markdown:Exercises are annotated with a comment containing the word "Exercise." The existing code compiles, however answers have
src/Course/List.hs:--   Exercises are generally increasing in difficulty, though some people may find later exercise easier.

$ git grep todo
projects/NetworkServer/haskell/src/Network/Server/Chat/Chat.hs:  error "todo"
projects/NetworkServer/haskell/src/Network/Server/Chat/Loop.hs:  error "todo"
projects/NetworkServer/haskell/src/Network/Server/TicTacToe/Game.hs:  error "todo"
projects/NetworkServer/haskell/src/Network/Server/TicTacToe/Game.hs:  error "todo"
src/Course/List.hs:--   Replace the function bodies (error "todo") with an appropriate solution.

So all the exercises appear to be pre-done. Am I missing some kind of prepatory step you need to do to actually generate the exercises?

L04.ListZipper.nth domain

The answer for the nth function gives strange results for negative indices:

test>> nth (-3) (ListZipper [9,8..0] 10 [])
[0,1,2,3,4,5,6]⋙7⋘[8,9,10]

I'd have expected ∅.

L04.ListZipper.getFocus

A function like

getFocus :: ListZipper' f => f a -> a

would seem to fit with the other *Focus functions and would make writing some tests simpler :) I see it's in the Comonad instance for ListZipper but that's almost at the end of the file.

Cabal test error: mkName not in scope

Hi,

Thanks for putting this course together. When I follow the instructions to run the tests, cabal test reports the error "Not in scope: `mkName'" many times. The final part of the output is given below.

<interactive>:3934:19: Not in scope: `mkName'
### Failure in src/Course/List.hs:290: expression `let types = x :: Int in reverse (x :. Nil) == x :. Nil'

<interactive>:3959:19: Not in scope: `mkName'
### Failure in src/Course/List.hs:317: expression `let types = x :: List Int in notReverse x ++ notReverse y == notReverse (y ++ x)'

<interactive>:4014:19: Not in scope: `mkName'
### Failure in src/Course/List.hs:319: expression `let types = x :: Int in notReverse (x :. Nil) == x :. Nil'

<interactive>:4039:19: Not in scope: `mkName'
Examples: 506  Tried: 277  Errors: 0  Failures: 192
Test suite doctests: FAIL
Test suite logged to: dist/test/course-0.1.1-doctests.log
0 of 1 test suites (0 of 1 test cases) passed.

I'm using a fresh install of the Haskell Platform (GHC 7.6.3) on Ubuntu 13.10. Am I missing a dependency?

Thanks,
Ben

ParseResult not equal with right applicative

I try to evaluate below

parse (valueParser 'v' *> character) "abc" vs parse ( character) "abc"

>> parse (valueParser 'v' *> character) "abc"
Result >bc< 'a'
>> parse (character) "abc"
Result >bc< 'a'

parse (character *> valueParser 'v') "abc" vs parse (valueParser 'v') "abc"

>> parse (character *> valueParser 'v') "abc"
Result >bc< 'v'
>> parse (valueParser 'v') "abc"
Result >abc< 'v'

Should parse (character *> valueParser 'v') "abc" vs parse (valueParser 'v') "abc" have the same result ?

Weird syntax error in State's Applicative instance

instance Applicative (State s) where
  pure ::
    a
    -> State s a
  pure a = State (\s -> (a, s))
  (<*>) ::
    State s (a -> b)
    -> State s a
    -> State s b 
  State f <*> State a =
    State (\s -> let (g, t) = f s
                    (z, u) = a t
                in (g z, u))

This is copied straight out of State.hs, but haskell-ide-engine complains of a syntax error on the ( of Iz, u). So does ghci when I try to load it (unsurprisingly).

L04.ListZipper.findLeft, findRight

This is more a suggestion.

I noticed that those two functions have a nice algebraic property, what I think is called a zero unit.

(u0 . findLeft f) z == u0 z where u0 = findLeft (const False)

and the same if u0 and findLeft f are swapped.

It'd be nice if they had an identity unit as well, like

(u1 . findLeft f) z == findLeft f z where u1 = findLeft (const True)

but they move at least one spot if they return a result. Would it make sense to change the spec and answers to have that property? If my terminology is off I'd like to be corrected. Cheers,

Ben

Implementation of Traversable for List makes Parser.string fail

Hi,

I've followed the whole course in the past weeks and completed almost everything. I had problems implementing the string function in the MoreParser.hs module. The code is::

string :: Chars -> Parser Chars
string = traverse is

I've coded all the tests described in the comments:

stringTest :: TestTree
stringTest =
  testGroup "string"
  [ testCase "parse the given string" $
    parse (string "") "" @?= Result "" ""
  , testCase "parse the given string" $
    parse (string "a") "a" @?= Result "" "a"
  ,  testCase "parse the given string" $
    parse (string "abc") "abc" @?= Result "" "abc"
  , testCase "fail otherwise" $
    parse (string "abc") "bcdef" @?= UnexpectedChar 'b'
  ]

and this is the output :

  string
    parse the given string:                                                    OK
    parse the given string:                                                    OK
    parse the given string:                                                    FAIL
      expected: Result >< "abc"
       but got: Unexpected character: "a"
    fail otherwise:                                                            OK

After some investigation et testing, i've come to realise that the traverse method for this list was not doing is job properly.

The actual solution is done by a foldRight here like this:

  traverse f = foldRight (\a b -> (:.) <$> f a <*> b) (pure Nil)

My solution to fix the error implies a slight change (that I don't fully understand yet):

  traverse f = foldRight (\a b -> flip (:.) <$> b <*> f a) (pure Nil)

I guess it's a lazyness thingy so I don't know if my solution is the one but there is something to fix somewhere 😃

ListZipper nth is doing more traversal than is necessary in some cases

In the case where the nth element on the left hand side, the entire left list is visited again via length, and none of the moving work that was already done is reused.

I think the following solution is near-optimal, but it may be possible to do better by keeping around the partial lists while you're trying to find the head. Sorry it's not very pretty, I'm doing this course to try to get better at haskell.

nth ::
  Int
  -> ListZipper a
  -> MaybeListZipper a
nth n origlz | n < 0 = IsNotZ
             | otherwise =
                  let (topList, llen) = countToHead origlz 0 in
                  if llen > n then moveRightN n topList
                  else moveRightN (n - llen) origlz
  where countToHead lz lacc = case moveLeft lz of
                              IsZ nlz -> countToHead nlz (lacc + 1)
                              IsNotZ  -> (lz, lacc)

Thanks for putting the course together. It's been great fun so far!

Use applicative style in Course.Parser

Applicative style of parsing will make the whole module cleaner.

I would also move instances of different classes directly below blbindParser so that they can be used in the subsequent exercises.

I could rework the module if this change would be accepted.

Usage of lens

The usage of lens in the network exercises (tic-tac-toe game and chat server) is not very friendly to beginners, I assume that the exercises are not about learn lens, but its usage makes it harder to understand the details.

How does Parse.list and Parse.list1 work?

How do they work if they reference each other?
Please explain, I can't ...

list1 k = (\a -> (\xs -> pure $ a :. xs) =<< list k) =<< k
list k = list1 k ||| valueParser Nil

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.